[{"data":1,"prerenderedAt":6621},["ShallowReactive",2],{"news":3,"latestArticles":47},[4],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"rowTypeId":12,"sitemap":13,"body":17,"_type":41,"_id":42,"_source":43,"_file":44,"_stem":45,"_extension":46},"/news/renewal_page","news",false,"","サイトリニューアルを実施しました","サイトリニューアル実施のお知らせです。今後もコンテンツの品質向上に努めます。","2025/01/31",1,{"loc":5,"lastmod":14,"priority":15,"changefreq":16},"2025-01-31",0.8,"monthly",{"type":18,"children":19,"toc":38},"root",[20,28,33],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25],{"type":26,"value":27},"text","2025年1月31日にサイトリニューアルを実施いたしました。",{"type":21,"tag":22,"props":29,"children":30},{},[31],{"type":26,"value":32},"この度のリニューアルでは、より使いやすく、見やすいデザインに一新し、皆様にとって快適にご利用いただけるように改善を行いました。また、新たなコンテンツや機能を追加し、皆様により充実した情報をお届けできるよう努めております。",{"type":21,"tag":22,"props":34,"children":35},{},[36],{"type":26,"value":37},"今後とも、どうぞよろしくお願い申し上げます。",{"title":8,"searchDepth":39,"depth":39,"links":40},2,[],"markdown","content:news:renewal_page.md","content","news/renewal_page.md","news/renewal_page","md",[48,1219,4419],{"_path":49,"_dir":50,"_draft":7,"_partial":7,"_locale":8,"title":51,"description":52,"date":53,"tags":54,"rowTypeId":12,"sitemap":60,"body":62,"_type":41,"_id":1216,"_source":43,"_file":1217,"_stem":1218,"_extension":46},"/articles/career/new-graduate-job-hunting","career","【27卒エンジニア就活】生殺与奪の権を他人に握らせない就活体験談","27卒WEB系エンジニアが逆求人だけで大手SaaS内定。財務分析・チェックシートで企業を比較した就活体験談。夏インターンから内定承諾まで全プロセスを公開。","2026/03/20",[55,56,57,58,59],"就活","新卒","エンジニア就活","キャリア","27卒",{"loc":49,"lastmod":61,"priority":12},"2026-03-20",{"type":18,"children":63,"toc":1185},[64,70,75,84,89,119,124,129,134,147,214,224,229,234,319,331,337,349,355,360,366,371,377,382,431,443,448,453,459,465,470,475,480,506,511,516,522,527,532,537,543,548,560,570,575,581,593,598,610,615,620,625,630,635,641,646,658,664,678,690,695,704,709,714,757,762,767,772,787,792,797,802,807,819,824,851,856,861,949,954,959,972,977,982,987,992,997,1007,1016,1021,1027,1035,1041,1046,1052,1057,1062,1102,1107,1112,1131,1143,1148,1165,1170,1175,1180],{"type":21,"tag":65,"props":66,"children":68},"h2",{"id":67},"はじめに",[69],{"type":26,"value":67},{"type":21,"tag":22,"props":71,"children":72},{},[73],{"type":26,"value":74},"この記事では、27卒WEB系エンジニアとして就活を経験した私が、就活の流れや重要だと感じたポイント、そして就活中に感じた不満とその対策をまとめています。エンジニアの就職活動はいつから何を準備すればいいのか分かりにくいため、これから就活を控えている方の参考になれば幸いです。",{"type":21,"tag":76,"props":77,"children":78},"summary-box",{},[79],{"type":21,"tag":22,"props":80,"children":81},{},[82],{"type":26,"value":83},"27卒WEB系エンジニアの就活体験談です。大学1年生から長期インターンで実務経験を積み、大学3年生の夏インターンを経て年内に内定を獲得しました。早く動くほど次のステップの選択肢が広がること、そして内定承諾は「人的資本の投資判断」として客観的な基準で行ったことをまとめています。",{"type":21,"tag":65,"props":85,"children":87},{"id":86},"対象読者",[88],{"type":26,"value":86},{"type":21,"tag":90,"props":91,"children":92},"ul",{},[93,99,104,109,114],{"type":21,"tag":94,"props":95,"children":96},"li",{},[97],{"type":26,"value":98},"WEB系エンジニアとして新卒採用での就活を考えている大学生",{"type":21,"tag":94,"props":100,"children":101},{},[102],{"type":26,"value":103},"エンジニア就活の全体像やスケジュール感を知りたい方",{"type":21,"tag":94,"props":105,"children":106},{},[107],{"type":26,"value":108},"サマーインターンや長期インターンシップについて情報を集めている方",{"type":21,"tag":94,"props":110,"children":111},{},[112],{"type":26,"value":113},"現在の就活事情を知りたい社会人",{"type":21,"tag":94,"props":115,"children":116},{},[117],{"type":26,"value":118},"就活生が何を考えているか知りたい人事担当者",{"type":21,"tag":65,"props":120,"children":122},{"id":121},"結果",[123],{"type":26,"value":121},{"type":21,"tag":22,"props":125,"children":126},{},[127],{"type":26,"value":128},"3社受けて2社から内定が得られました。全社サマーインターン経由の選考です。1社の落選はカルチャーミスマッチが大きかったかもと感じています。",{"type":21,"tag":65,"props":130,"children":132},{"id":131},"就活のプロセス",[133],{"type":26,"value":131},{"type":21,"tag":22,"props":135,"children":136},{},[137,139,145],{"type":26,"value":138},"実質的に",{"type":21,"tag":140,"props":141,"children":142},"strong",{},[143],{"type":26,"value":144},"就活は大学1年生から始まりました",{"type":26,"value":146},"。",{"type":21,"tag":148,"props":149,"children":150},"timeline",{},[151],{"type":21,"tag":90,"props":152,"children":153},{},[154,164,174,184,194,204],{"type":21,"tag":94,"props":155,"children":156},{},[157,162],{"type":21,"tag":140,"props":158,"children":159},{},[160],{"type":26,"value":161},"大学1年生 4月〜",{"type":26,"value":163},"\nエンジニア長期インターン探し",{"type":21,"tag":94,"props":165,"children":166},{},[167,172],{"type":21,"tag":140,"props":168,"children":169},{},[170],{"type":26,"value":171},"大学1年生 夏",{"type":26,"value":173},"\nエンジニア長期インターン開始",{"type":21,"tag":94,"props":175,"children":176},{},[177,182],{"type":21,"tag":140,"props":178,"children":179},{},[180],{"type":26,"value":181},"大学2〜3年生の春",{"type":26,"value":183},"\nサマーインターン探し・選考",{"type":21,"tag":94,"props":185,"children":186},{},[187,192],{"type":21,"tag":140,"props":188,"children":189},{},[190],{"type":26,"value":191},"大学3年生 夏",{"type":26,"value":193},"\nサマーインターン（就業型2社、ワークショップ型1社）",{"type":21,"tag":94,"props":195,"children":196},{},[197,202],{"type":21,"tag":140,"props":198,"children":199},{},[200],{"type":26,"value":201},"大学3年生 秋",{"type":26,"value":203},"\n本選考",{"type":21,"tag":94,"props":205,"children":206},{},[207,212],{"type":21,"tag":140,"props":208,"children":209},{},[210],{"type":26,"value":211},"大学3年生 11月",{"type":26,"value":213},"\n内定",{"type":21,"tag":22,"props":215,"children":216},{},[217,222],{"type":21,"tag":140,"props":218,"children":219},{},[220],{"type":26,"value":221},"大学3年生かつ年内で内定が得られる時代",{"type":26,"value":223},"になりました。",{"type":21,"tag":65,"props":225,"children":227},{"id":226},"就活で感じた重要ポイント",[228],{"type":26,"value":226},{"type":21,"tag":22,"props":230,"children":231},{},[232],{"type":26,"value":233},"エンジニア就活で重要だと感じたポイントをまとめました。各ポイントの詳細は後続のMISSIONで振り返っています。",{"type":21,"tag":235,"props":236,"children":237},"ol",{},[238,268,291,304,309,314],{"type":21,"tag":94,"props":239,"children":240},{},[241,243,248,250],{"type":26,"value":242},"スキル面は",{"type":21,"tag":140,"props":244,"children":245},{},[246],{"type":26,"value":247},"実務経験やチームでの開発経験が何よりも求められた",{"type":26,"value":249}," → MISSION 1で詳述\n",{"type":21,"tag":235,"props":251,"children":252},{},[253,258,263],{"type":21,"tag":94,"props":254,"children":255},{},[256],{"type":26,"value":257},"個人開発の「結果」はあまり評価されなかった。その過程を説明できるとGoodだった",{"type":21,"tag":94,"props":259,"children":260},{},[261],{"type":26,"value":262},"実務経験を積む機会がない場合は大学のサークルなどを活用してチームで開発する経験があると良かったと思う",{"type":21,"tag":94,"props":264,"children":265},{},[266],{"type":26,"value":267},"自分が何をしたいかをしっかり語れるようにすることが重要だった。「技術力を磨いてテックリードになりたいです」ではなく、「どういうものを作って、どういうことを成し遂げたいか」というような野望を語れるかが問われた",{"type":21,"tag":94,"props":269,"children":270},{},[271,276,278],{"type":21,"tag":140,"props":272,"children":273},{},[274],{"type":26,"value":275},"夏インターンは実質本選考のプロセスだった",{"type":26,"value":277}," → MISSION 2で詳述\n",{"type":21,"tag":235,"props":279,"children":280},{},[281,286],{"type":21,"tag":94,"props":282,"children":283},{},[284],{"type":26,"value":285},"企業が優秀な学生を囲い込むためのイベントだった",{"type":21,"tag":94,"props":287,"children":288},{},[289],{"type":26,"value":290},"優秀というのは現在のスキルが多い人ではなく、今後成長できそうという意味。新卒なので",{"type":21,"tag":94,"props":292,"children":293},{},[294,296],{"type":26,"value":295},"夏インターンの募集は大学3年生の4月頃から始まった\n",{"type":21,"tag":235,"props":297,"children":298},{},[299],{"type":21,"tag":94,"props":300,"children":301},{},[302],{"type":26,"value":303},"つまり、大学2年生までにある程度の準備が必要だった",{"type":21,"tag":94,"props":305,"children":306},{},[307],{"type":26,"value":308},"夏インターン(特に就業型)の募集枠は小さかった",{"type":21,"tag":94,"props":310,"children":311},{},[312],{"type":26,"value":313},"夏インターンで結果を残せたことで本選考で優遇された",{"type":21,"tag":94,"props":315,"children":316},{},[317],{"type":26,"value":318},"夏インターン以上に本選考では厳しく査定された → MISSION 3で詳述",{"type":21,"tag":22,"props":320,"children":321},{},[322,324,329],{"type":26,"value":323},"このように、",{"type":21,"tag":140,"props":325,"children":326},{},[327],{"type":26,"value":328},"夏インターンに参加できるかがカギ",{"type":26,"value":330},"でした。さらに、内定をもらった後も「どの企業に自分の時間を投資するか」という判断が待っています。そこで、実際に就活時に設定していた3つのMISSIONを紹介します。",{"type":21,"tag":65,"props":332,"children":334},{"id":333},"mission-１実務経験を得よ",[335],{"type":26,"value":336},"MISSION １：実務経験を得よ",{"type":21,"tag":22,"props":338,"children":339},{},[340,342,347],{"type":26,"value":341},"夏インターンの選考でも、本選考でも、実務経験に関する内容はよく聞かれました。",{"type":21,"tag":140,"props":343,"children":344},{},[345],{"type":26,"value":346},"実務経験があるというだけで就活難易度が大きく変わった",{"type":26,"value":348},"と感じたので、積極的に狙いました。",{"type":21,"tag":350,"props":351,"children":353},"h3",{"id":352},"どうやって探したか",[354],{"type":26,"value":352},{"type":21,"tag":22,"props":356,"children":357},{},[358],{"type":26,"value":359},"InfraやWantedlyなどのサイトを利用しました。求人は多くありましたが、未経験OKな求人を探すのは難しかったです。",{"type":21,"tag":350,"props":361,"children":363},{"id":362},"実務経験が欲しい理由チームでの経験が重要だから",[364],{"type":26,"value":365},"実務経験が欲しい理由：チームでの経験が重要だから",{"type":21,"tag":22,"props":367,"children":368},{},[369],{"type":26,"value":370},"個人開発と実務は全然違うと感じました。特に、「どのくらいのチーム規模でどのようにコミュニケーションをとったか」、「チームで働きやすい人と働きにくい人の違い」というような個人ではなく集団でどのように動いたかという質問は本選考でもよく聞かれました。具体的に個人開発と何が違うか感じたことをまとめます。",{"type":21,"tag":372,"props":373,"children":375},"h4",{"id":374},"個人開発と実務の違い",[376],{"type":26,"value":374},{"type":21,"tag":22,"props":378,"children":379},{},[380],{"type":26,"value":381},"ソースコードの規模が違う程度だと最初は思っていましたが、その程度ではありません。何が違うか。それは、自由度が全然違うということです。",{"type":21,"tag":383,"props":384,"children":387},"tradeoff-box",{"cons-label":385,"pros-label":386},"実務の特徴","個人開発の特徴",[388,410],{"type":21,"tag":389,"props":390,"children":391},"template",{"v-slot:pros":8},[392],{"type":21,"tag":90,"props":393,"children":394},{},[395,400,405],{"type":21,"tag":94,"props":396,"children":397},{},[398],{"type":26,"value":399},"好きな技術で開発できる",{"type":21,"tag":94,"props":401,"children":402},{},[403],{"type":26,"value":404},"自由に時間をかけられる",{"type":21,"tag":94,"props":406,"children":407},{},[408],{"type":26,"value":409},"作りたいものが作れる",{"type":21,"tag":389,"props":411,"children":412},{"v-slot:cons":8},[413],{"type":21,"tag":90,"props":414,"children":415},{},[416,421,426],{"type":21,"tag":94,"props":417,"children":418},{},[419],{"type":26,"value":420},"既存の技術を踏襲する必要がある",{"type":21,"tag":94,"props":422,"children":423},{},[424],{"type":26,"value":425},"納期（いつまでに）の制約がある",{"type":21,"tag":94,"props":427,"children":428},{},[429],{"type":26,"value":430},"泥臭い制約が多い",{"type":21,"tag":22,"props":432,"children":433},{},[434,436,441],{"type":26,"value":435},"個人開発ではやりたい放題できましたが、実務ではこのような制約が多くありました。これを経験したかしていないかで大きな違いが出るので実務経験は必要とされていました。企業から求められる技術力というのは",{"type":21,"tag":140,"props":437,"children":438},{},[439],{"type":26,"value":440},"キラキラした経験ではなく、くすんだ経験の積み重ね",{"type":26,"value":442},"でした。",{"type":21,"tag":22,"props":444,"children":445},{},[446],{"type":26,"value":447},"重要ポイント1で述べた「実務経験やチームでの開発経験が何よりも求められた」というのは、こうした背景があったからです。",{"type":21,"tag":22,"props":449,"children":450},{},[451],{"type":26,"value":452},"また、これは大学1年生当時は予想できておらず結果論になりますが、AIの技術が以上に発展し、個人開発で評価を得る難易度が高くなっているというのも実務経験が欲しい理由になると思います。詳細は以下の記事をご覧ください。",{"type":21,"tag":454,"props":455,"children":458},"link-card",{"label":456,"to":457},"個人開発とAIについてはこちら⬇️","/articles/tech/development/portal-with-claude-code",[],{"type":21,"tag":65,"props":460,"children":462},{"id":461},"mission-2夏インターン先を確保せよ",[463],{"type":26,"value":464},"MISSION 2：夏インターン先を確保せよ",{"type":21,"tag":22,"props":466,"children":467},{},[468],{"type":26,"value":469},"夏インターンは参加すれば色々お得だったので、必ず参加したいと考えていました。",{"type":21,"tag":350,"props":471,"children":473},{"id":472},"サマーインターン参加のメリット",[474],{"type":26,"value":472},{"type":21,"tag":22,"props":476,"children":477},{},[478],{"type":26,"value":479},"以下のメリットがあると感じました。",{"type":21,"tag":235,"props":481,"children":482},{},[483,496,501],{"type":21,"tag":94,"props":484,"children":485},{},[486,488],{"type":26,"value":487},"実際に働くことで、会社の雰囲気や文化を感じられた\n",{"type":21,"tag":235,"props":489,"children":490},{},[491],{"type":21,"tag":94,"props":492,"children":493},{},[494],{"type":26,"value":495},"インターン向けに良い部分だけを見せるという会社は少ないのである程度信用できました。内定承諾先を選ぶ際にも役立ちました",{"type":21,"tag":94,"props":497,"children":498},{},[499],{"type":26,"value":500},"給料ももらえた（時給2000円〜が相場）",{"type":21,"tag":94,"props":502,"children":503},{},[504],{"type":26,"value":505},"本選考の一部をスキップできた",{"type":21,"tag":350,"props":507,"children":509},{"id":508},"どうやって探したか-1",[510],{"type":26,"value":352},{"type":21,"tag":22,"props":512,"children":513},{},[514],{"type":26,"value":515},"サポーターズの1on1イベントなど、逆求人イベントを活用しました。実務経験があったので参加するための選考は比較的簡単に通りました。そこで多くの企業と面談して、「自分はどのような会社で働いて、どのようなエンジニアになりたいか」自問自答しました。",{"type":21,"tag":372,"props":517,"children":519},{"id":518},"就活時短ポイントなぜ逆求人",[520],{"type":26,"value":521},"就活時短ポイント！なぜ逆求人？",{"type":21,"tag":22,"props":523,"children":524},{},[525],{"type":26,"value":526},"逆求人イベントは選考スキップがもらえることがあります。具体的には、書類選考スキップ、面接スキップなどがあります。",{"type":21,"tag":350,"props":528,"children":530},{"id":529},"サマーインターン選考対策",[531],{"type":26,"value":529},{"type":21,"tag":22,"props":533,"children":534},{},[535],{"type":26,"value":536},"「自分がエンジニアとして何を大事にしているのか」というようなマインドセット、そして「どのような開発をしてきてどのように考えたか」というような技術力を具体的なエピソードとともに語れるように準備しました。志望理由はまだ技術的な目的（貴社の〇〇の開発を通して△△を学びたい）程度で十分でした。",{"type":21,"tag":372,"props":538,"children":540},{"id":539},"面接対策は一問一答ではなく木構造で考える",[541],{"type":26,"value":542},"面接対策は「一問一答」ではなく木構造で考える",{"type":21,"tag":22,"props":544,"children":545},{},[546],{"type":26,"value":547},"少しエンジニアっぽい考えを選考対策に加えます。\n面接対策としてよくあるのは「質問と回答を暗記する」という方法ですが、私はあまり効果的ではないと感じました。\n面接ではほぼ確実に深掘りが入ります。\nそのため、一問一答で回答を準備しても途中で破綻します。",{"type":21,"tag":22,"props":549,"children":550},{},[551,553,558],{"type":26,"value":552},"そこで私は、回答を ",{"type":21,"tag":140,"props":554,"children":555},{},[556],{"type":26,"value":557},"木構造（ツリー構造）",{"type":26,"value":559}," で整理しました。",{"type":21,"tag":561,"props":562,"children":564},"pre",{"code":563},"例：  \n・なぜエンジニアを志望したのか(よくある質問ですね)  \n　├ きっかけ  \n　├ どのような経験をしてきたか（ここが重要！）  \n　└ 将来どのようなものを作りたいか  \n",[565],{"type":21,"tag":566,"props":567,"children":568},"code",{"__ignoreMap":8},[569],{"type":26,"value":563},{"type":21,"tag":22,"props":571,"children":572},{},[573],{"type":26,"value":574},"このように背景・経験・将来像をセットで整理しておくと、どの方向に深掘りされても一貫した回答ができました。",{"type":21,"tag":372,"props":576,"children":578},{"id":577},"結果だけでは評価されない逆にありがたい",[579],{"type":26,"value":580},"結果だけでは評価されない(逆にありがたい)",{"type":21,"tag":22,"props":582,"children":583},{},[584,586,591],{"type":26,"value":585},"ここでいう、結果というのは「〇〇を開発した」、「インターンに参加した」、「〇〇の資格を取った」というようなものです。\nどこの企業でも ",{"type":21,"tag":140,"props":587,"children":588},{},[589],{"type":26,"value":590},"「プロセス」に興味を持ちます",{"type":26,"value":592},"。何がきっかけで、どのようなことをして、どのような結果になって、どう感じたか。\n就活以前にこれが説明できないとどうしても胡散臭い自慢話になってしまうと感じました。\nあと、私は遭遇していませんが、もし結果だけを評価してくる企業があったとしたら、入社後にメンタルを潰されると思います。",{"type":21,"tag":372,"props":594,"children":596},{"id":595},"たくさん選考を受けるのは重要だった",[597],{"type":26,"value":595},{"type":21,"tag":22,"props":599,"children":600},{},[601,603,608],{"type":26,"value":602},"面接に慣れることが何よりも大事だと感じました。自分が伝えたいことをしっかり伝えるためには何よりも",{"type":21,"tag":140,"props":604,"children":605},{},[606],{"type":26,"value":607},"面接慣れ",{"type":26,"value":609},"が重要でした。",{"type":21,"tag":372,"props":611,"children":613},{"id":612},"鋼の精神を持った",[614],{"type":26,"value":612},{"type":21,"tag":22,"props":616,"children":617},{},[618],{"type":26,"value":619},"夏インターン選考では一部企業では落選でした。ですが、ここで落ち込むと他企業の選考に響くので、単に縁がなかっただけと割り切りました。",{"type":21,"tag":22,"props":621,"children":622},{},[623],{"type":26,"value":624},"これを言うと人事担当者に怒られそうですが、「夏インターンの選考落としてきた企業は、絶対本選考も受けない」と心に決めていたのでそれくらいの厚顔無恥精神で戦っていました。「生殺与奪の権を他人に握らせるな」ということですね！",{"type":21,"tag":22,"props":626,"children":627},{},[628],{"type":26,"value":629},"※あくまで長期戦で精神を強く保つためです。貴重な時間をいただいているのでしっかり感謝の気持ちを持ちましょう。ちなみに、夏インターンに落ちても本選考では合格という例もあるらしいので機会損失には気をつけてください。",{"type":21,"tag":22,"props":631,"children":632},{},[633],{"type":26,"value":634},"重要ポイント2〜5で述べたように、夏インターンは実質本選考のプロセスであり、ここで実績を作れるかが就活全体の流れを左右しました。",{"type":21,"tag":65,"props":636,"children":638},{"id":637},"mission-3本選考を突破し内定を承諾せよ",[639],{"type":26,"value":640},"MISSION 3：本選考を突破し、内定を承諾せよ",{"type":21,"tag":22,"props":642,"children":643},{},[644],{"type":26,"value":645},"本選考の対策自体はサマーインターンと同じ選考対策に加え、志望動機の言語化ができていれば問題ありませんでした。",{"type":21,"tag":22,"props":647,"children":648},{},[649,651,656],{"type":26,"value":650},"しかし、本選考を進める中で強く感じたのは、",{"type":21,"tag":140,"props":652,"children":653},{},[654],{"type":26,"value":655},"合格をもらうことがゴールではない",{"type":26,"value":657},"ということでした。ゴールは最適なキャリアを選択することです。できる限り転職せずにキャリアを積みたいと考えていたので、この辺りからキャリアプラン・ライフプラン・企業文化などがマッチングしているかを考え始めました。夏インターンはあくまで選択肢を増やすためでしたが、本選考以降は選択肢を絞っていきました。",{"type":21,"tag":350,"props":659,"children":661},{"id":660},"内定承諾は人的資本への投資",[662],{"type":26,"value":663},"内定承諾は「人的資本への投資」",{"type":21,"tag":22,"props":665,"children":666},{},[667,669,676],{"type":26,"value":668},"内定承諾というのは、単に会社を選ぶという行為ではありません。\n自分という",{"type":21,"tag":670,"props":671,"children":673},"tooltip",{"content":672},"自分のスキル・時間・労働力を経済的な資本として捉える考え方",[674],{"type":26,"value":675},"人的資本",{"type":26,"value":677},"をどの企業に投資するかを決める意思決定だと考えていました。",{"type":21,"tag":22,"props":679,"children":680},{},[681,683,688],{"type":26,"value":682},"社会人として働く時間は人生の大きな割合を占めます。学生時代も小学校から大学まで16年ですから。その時間をどの企業に投じるかによって、身につくスキル・経験・人脈は大きく変わります。そのため、企業選びをできるだけ",{"type":21,"tag":140,"props":684,"children":685},{},[686],{"type":26,"value":687},"感覚ではなく、客観的な基準で判断",{"type":26,"value":689},"したいと考えました。",{"type":21,"tag":22,"props":691,"children":692},{},[693],{"type":26,"value":694},"この考えのもと、納得のいく選択をするために実際にやったことを紹介します。",{"type":21,"tag":696,"props":697,"children":698},"caution-box",{},[699],{"type":21,"tag":22,"props":700,"children":701},{},[702],{"type":26,"value":703},"ここから先は人事担当者の方は閲覧注意です",{"type":21,"tag":350,"props":705,"children":707},{"id":706},"自分が何をしたいかを再言語化した",[708],{"type":26,"value":706},{"type":21,"tag":22,"props":710,"children":711},{},[712],{"type":26,"value":713},"志望動機にも関わるのでこれは最優先で進めました。自分が何をしたくて、この企業ではこういうことができて……というのを言語化しました。具体的には以下のような観点でまとめました。",{"type":21,"tag":90,"props":715,"children":716},{},[717,727,737,747],{"type":21,"tag":94,"props":718,"children":719},{},[720,725],{"type":21,"tag":140,"props":721,"children":722},{},[723],{"type":26,"value":724},"どのようなものを作りたいか",{"type":26,"value":726},": 技術を手段として何を実現したいのか",{"type":21,"tag":94,"props":728,"children":729},{},[730,735],{"type":21,"tag":140,"props":731,"children":732},{},[733],{"type":26,"value":734},"どのようなエンジニアになりたいか",{"type":26,"value":736},": 目指すキャリアパスの方向性（マネジメント寄りか技術寄りかなど）",{"type":21,"tag":94,"props":738,"children":739},{},[740,745],{"type":21,"tag":140,"props":741,"children":742},{},[743],{"type":26,"value":744},"働く上で何を大事にしたいか",{"type":26,"value":746},": 企業文化やチームの雰囲気に求めるもの",{"type":21,"tag":94,"props":748,"children":749},{},[750,755],{"type":21,"tag":140,"props":751,"children":752},{},[753],{"type":26,"value":754},"中長期的なライフプランとの整合性",{"type":26,"value":756},": 働き方（リモート可否）や勤務地が将来の生活設計と合うか",{"type":21,"tag":22,"props":758,"children":759},{},[760],{"type":26,"value":761},"これだけで2000文字程度のレポートになりました。この言語化が後の企業比較レポートやチェックシートの評価軸にもそのまま使えたので、最初にやっておいて正解でした。",{"type":21,"tag":454,"props":763,"children":766},{"label":764,"to":765},"「何をしたいか」の言語化についてはこちら⬇️","/articles/career/what-do-i-want-to-do",[],{"type":21,"tag":350,"props":768,"children":770},{"id":769},"定量評価するためのチェックシートを作成した",[771],{"type":26,"value":769},{"type":21,"tag":22,"props":773,"children":774},{},[775,781,785],{"type":21,"tag":776,"props":777,"children":780},"img",{"alt":778,"src":779},"チェックシート","/images/articles/career/checksheet.png",[],{"type":21,"tag":782,"props":783,"children":784},"br",{},[],{"type":26,"value":786},"\n画像のようなチェックシートを作成し、点数づけ、その点数をつけた理由を言語化しました。価値観がぶれないようにするためにも重要な指標でした。",{"type":21,"tag":22,"props":788,"children":789},{},[790],{"type":26,"value":791},"なお、技術スタックの魅力・先進性の重み値はあえて0にしました。これを意識する人は多いですが、一番効率よく学べる環境というのは最新技術を導入しているとか表面的なものではなく、文化・人・制度という技術とは切り離した環境だと考えています。また、どんなに良い環境でも成長できない人はできないままなので自力でなんとかするという考えがベースにあります。",{"type":21,"tag":350,"props":793,"children":795},{"id":794},"リクルーターとの面談で懸念点を探った",[796],{"type":26,"value":794},{"type":21,"tag":22,"props":798,"children":799},{},[800],{"type":26,"value":801},"この辺のすり合わせは大事だと感じました。選考で落ちるリスクは増えますが、誤った選択をするリスクは減ります。この辺を忖度する必要は一切ないと思います。自分の人生は自分のものですから。",{"type":21,"tag":350,"props":803,"children":805},{"id":804},"内定後に企業比較レポートを書いた",[806],{"type":26,"value":804},{"type":21,"tag":22,"props":808,"children":809},{},[810,812,817],{"type":26,"value":811},"内定承諾先を",{"type":21,"tag":140,"props":813,"children":814},{},[815],{"type":26,"value":816},"感覚ではなくデータに基づいて判断",{"type":26,"value":818},"したかったので、内定先を比較するレポートを作成しました。最終的に5000文字規模になりましたが、書き上げたことで「なぜこの企業を選ぶのか」を自分自身に対して明確に説明できる状態になりました。以下のような観点で分析しました。",{"type":21,"tag":372,"props":820,"children":822},{"id":821},"給与構造の分析",[823],{"type":26,"value":821},{"type":21,"tag":22,"props":825,"children":826},{},[827,829,834,836,842,844,849],{"type":26,"value":828},"単純に年収の額面を比較するだけでなく、月給のうち手当が占める割合（手当比率）や時間単価を算出しました。",{"type":21,"tag":140,"props":830,"children":831},{},[832],{"type":26,"value":833},"手当比率が高いと、手当は本給と比べて変更されやすいため減給リスクが大きくなります",{"type":26,"value":835},"。また、",{"type":21,"tag":670,"props":837,"children":839},{"content":838},"あらかじめ一定時間分の残業代を給与に含めて支払う制度。みなし残業代とも呼ばれる",[840],{"type":26,"value":841},"固定残業代",{"type":26,"value":843},"から逆算して時間単価を比較することで、残業が増えた場合のリスクも見積もりました。最近は",{"type":21,"tag":140,"props":845,"children":846},{},[847],{"type":26,"value":848},"固定残業代で額面を大きく見せている企業が多い",{"type":26,"value":850},"のでしっかり確認しました。",{"type":21,"tag":372,"props":852,"children":854},{"id":853},"ビジネスモデルと財務の分析",[855],{"type":26,"value":853},{"type":21,"tag":22,"props":857,"children":858},{},[859],{"type":26,"value":860},"半期報告書などのIR情報をもとに、売上高の成長率・純利益率・自己資本比率などを比較しました。上場企業であれば公開が義務付けられており、投資家向けの情報なので会社がどの方向に向かうかが一番わかりやすく書かれていました。",{"type":21,"tag":862,"props":863,"children":864},"table",{},[865,884],{"type":21,"tag":866,"props":867,"children":868},"thead",{},[869],{"type":21,"tag":870,"props":871,"children":872},"tr",{},[873,879],{"type":21,"tag":874,"props":875,"children":876},"th",{},[877],{"type":26,"value":878},"項目",{"type":21,"tag":874,"props":880,"children":881},{},[882],{"type":26,"value":883},"わかること",{"type":21,"tag":885,"props":886,"children":887},"tbody",{},[888,906,923,936],{"type":21,"tag":870,"props":889,"children":890},{},[891,901],{"type":21,"tag":892,"props":893,"children":894},"td",{},[895],{"type":21,"tag":670,"props":896,"children":898},{"content":897},"総資産のうち返済不要な自己資本の割合。高いほど財務が安定",[899],{"type":26,"value":900},"自己資本比率",{"type":21,"tag":892,"props":902,"children":903},{},[904],{"type":26,"value":905},"財務の安定性。高いほど借入に依存しておらず倒産リスクが低い",{"type":21,"tag":870,"props":907,"children":908},{},[909,918],{"type":21,"tag":892,"props":910,"children":911},{},[912],{"type":21,"tag":670,"props":913,"children":915},{"content":914},"売上高に対する本業の利益の割合",[916],{"type":26,"value":917},"営業利益率",{"type":21,"tag":892,"props":919,"children":920},{},[921],{"type":26,"value":922},"本業の稼ぐ力。高いほど事業そのものが儲かっている",{"type":21,"tag":870,"props":924,"children":925},{},[926,931],{"type":21,"tag":892,"props":927,"children":928},{},[929],{"type":26,"value":930},"純利益率",{"type":21,"tag":892,"props":932,"children":933},{},[934],{"type":26,"value":935},"最終的な収益性。税金や特別損失を含めた総合的な利益体質",{"type":21,"tag":870,"props":937,"children":938},{},[939,944],{"type":21,"tag":892,"props":940,"children":941},{},[942],{"type":26,"value":943},"参入障壁の高さ",{"type":21,"tag":892,"props":945,"children":946},{},[947],{"type":26,"value":948},"競合の入りにくさ。高いほど価格競争に巻き込まれにくく事業が安定する",{"type":21,"tag":22,"props":950,"children":951},{},[952],{"type":26,"value":953},"特に自己資本比率は高いほど健全な財務といえますが、企業のフェーズ（グロースフェーズ）であれば低くなりがちなのであくまで参考程度にした方が良いと思います。また、ビジネスモデルやサプライチェーンによっても営業利益率も変わってくるので、単純に高い方が優れていると比較できるわけではないことに注意しました。なので基本的な企業研究が済んでいる前提で行った方が良いですね。",{"type":21,"tag":372,"props":955,"children":957},{"id":956},"資産形成シミュレーション",[958],{"type":26,"value":956},{"type":21,"tag":22,"props":960,"children":961},{},[962,964,970],{"type":26,"value":963},"持株会の奨励金率やストックオプションの条件を比較し、長期的にどちらが低リスクで資産形成できるかを検討しました。",{"type":21,"tag":670,"props":965,"children":967},{"content":966},"同じ金額でも早く手に入れるほど運用期間が長くなり価値が高いという考え方",[968],{"type":26,"value":969},"貨幣の時間価値など",{"type":26,"value":971},"（若いうちに得る収入とそれによって得られる経験ほど複利が利く）の観点も加えました。20歳で得る100万円と50歳で得る100万円ではできることが違いますからね。\n資産形成の話は以下の記事にまとめたのでぜひご覧ください。大学生から考えてても早すぎない状況になっていると思います。",{"type":21,"tag":454,"props":973,"children":976},{"label":974,"to":975},"資産形成の詳細はこちら⬇️","/articles/career/student-asset-building",[],{"type":21,"tag":372,"props":978,"children":980},{"id":979},"ライフプランとの整合性",[981],{"type":26,"value":979},{"type":21,"tag":22,"props":983,"children":984},{},[985],{"type":26,"value":986},"勤務形態（リモート可否）、転職時の不利にならないか（本給の水準）、住宅ローンの借入可能額への影響など、中長期的なライフステージの変化を見据えた比較も行いました。",{"type":21,"tag":22,"props":988,"children":989},{},[990],{"type":26,"value":991},"重要ポイント6で「夏インターン以上に本選考では厳しく査定された」と述べましたが、それは選考の難易度だけでなく、自分自身の判断基準もより厳しくなったという意味でもあります。選考を突破するだけでなく、その先の「どの企業に人生を投資するか」まで考え抜いたのがこのMISSIONでした。",{"type":21,"tag":350,"props":993,"children":995},{"id":994},"運命の選択",[996],{"type":26,"value":994},{"type":21,"tag":22,"props":998,"children":999},{},[1000,1002,1005],{"type":26,"value":1001},"ここまでチェックシート、レポート、財務分析までしてきました。材料は出揃い、完璧なはずです。",{"type":21,"tag":782,"props":1003,"children":1004},{},[],{"type":26,"value":1006},"\nしかし！",{"type":21,"tag":1008,"props":1009,"children":1010},"punchline-box",{},[1011],{"type":21,"tag":22,"props":1012,"children":1013},{},[1014],{"type":26,"value":1015},"あれ？両方とも楽しそうじゃね？",{"type":21,"tag":22,"props":1017,"children":1018},{},[1019],{"type":26,"value":1020},"というように結局迷う事になりました。\n気持ち的には分身をもう１人用意してそれぞれで内定承諾させたかったです(ついでに収入もほぼ２倍ですからね。分身すればお得です)。",{"type":21,"tag":65,"props":1022,"children":1024},{"id":1023},"エンジニア就活ブチギレポイント対策",[1025],{"type":26,"value":1026},"エンジニア就活ブチギレポイント・対策",{"type":21,"tag":696,"props":1028,"children":1029},{},[1030],{"type":21,"tag":22,"props":1031,"children":1032},{},[1033],{"type":26,"value":1034},"ここから先はかなり過激です。",{"type":21,"tag":350,"props":1036,"children":1038},{"id":1037},"長期インターンシップの募集は実務経験必須の募集が多い",[1039],{"type":26,"value":1040},"長期インターンシップの募集は実務経験必須の募集が多い！",{"type":21,"tag":22,"props":1042,"children":1043},{},[1044],{"type":26,"value":1045},"よくある「鶏と卵どちらが先か」という問題です。実務経験が欲しくて長期インターンシップを探しているのに実務経験を求められるのが困りました。これに関しては文句を言っても仕方がないので頑張って求人を探しました。この社会で生きると言うことは理不尽な矛盾との戦いに出るということですね。就活も例外ではありません。",{"type":21,"tag":350,"props":1047,"children":1049},{"id":1048},"こんな早い時期に就活をさせて学生に何を求めてるんだ",[1050],{"type":26,"value":1051},"こんな早い時期に就活をさせて、学生に何を求めてるんだ！！",{"type":21,"tag":22,"props":1053,"children":1054},{},[1055],{"type":26,"value":1056},"2年生から3年生にかけての春休みから就活が始まるのは正直かなり早いと感じました。\nまだ研究室にも配属されていない時期です。\nしかもESや面接では大学の成績について聞かれることはほぼありませんでした。\nこれを経験して「大学とは何なのか」と哲学的な問いを抱えた就活生は私だけではないはず。\nなので、大学は学問を学ぶ場所ではなく就活予備校だと思っていた方が精神衛生上良いと思います。大学の授業は役に立ちます。ですが、3年生でまだ授業が多くある中、就活と並行して力を入れるのは無理です。このようにリソース不足に追いやるのが現代の就職活動なのです。周りの大人が「大学は学問がー」とか講釈を垂れようと自分の意思を貫きましょう。時代は変わりました。",{"type":21,"tag":22,"props":1058,"children":1059},{},[1060],{"type":26,"value":1061},"就活早期化のメリット・デメリットをまとめると以下になります。",{"type":21,"tag":383,"props":1063,"children":1064},{},[1065,1076],{"type":21,"tag":389,"props":1066,"children":1067},{"v-slot:pros":8},[1068],{"type":21,"tag":90,"props":1069,"children":1070},{},[1071],{"type":21,"tag":94,"props":1072,"children":1073},{},[1074],{"type":26,"value":1075},"大学4年生では卒業研究だけに集中できる",{"type":21,"tag":389,"props":1077,"children":1078},{"v-slot:cons":8},[1079],{"type":21,"tag":90,"props":1080,"children":1081},{},[1082,1087,1092,1097],{"type":21,"tag":94,"props":1083,"children":1084},{},[1085],{"type":26,"value":1086},"2年→3年の春休みから就活開始で早すぎる",{"type":21,"tag":94,"props":1088,"children":1089},{},[1090],{"type":26,"value":1091},"研究室配属前なのに就活が始まる",{"type":21,"tag":94,"props":1093,"children":1094},{},[1095],{"type":26,"value":1096},"大学の成績はほぼ聞かれない",{"type":21,"tag":94,"props":1098,"children":1099},{},[1100],{"type":26,"value":1101},"リソース不足に陥りやすい",{"type":21,"tag":22,"props":1103,"children":1104},{},[1105],{"type":26,"value":1106},"個人的にデメリットが大きく感じました。3年生で就活するのはしんどいので勘弁してくれというのが本音です。売り手市場なのはありがたいですがやりすぎだと思います。",{"type":21,"tag":65,"props":1108,"children":1110},{"id":1109},"結論",[1111],{"type":26,"value":1109},{"type":21,"tag":22,"props":1113,"children":1114},{},[1115,1117,1122,1124,1129],{"type":26,"value":1116},"エンジニアの就職活動を振り返って最も伝えたいのは、",{"type":21,"tag":140,"props":1118,"children":1119},{},[1120],{"type":26,"value":1121},"早く動き始めること",{"type":26,"value":1123},"と",{"type":21,"tag":140,"props":1125,"children":1126},{},[1127],{"type":26,"value":1128},"自分の判断軸を持つこと",{"type":26,"value":1130},"の2つです。",{"type":21,"tag":22,"props":1132,"children":1133},{},[1134,1136,1141],{"type":26,"value":1135},"MISSION 1で実務経験を早期に積んだことがMISSION 2の夏インターン選考を有利にし、MISSION 2で得た経験と実績がMISSION 3の本選考・内定承諾の判断材料になりました。このように、",{"type":21,"tag":140,"props":1137,"children":1138},{},[1139],{"type":26,"value":1140},"早く動くほど次のステップの選択肢が広がる構造",{"type":26,"value":1142},"になっていました。",{"type":21,"tag":22,"props":1144,"children":1145},{},[1146],{"type":26,"value":1147},"そして、自分が何をしたいかを言語化し、チェックシートやレポートで客観的に評価したことで、内定承諾の判断に後悔がなくなりました。",{"type":21,"tag":22,"props":1149,"children":1150},{},[1151,1156,1158,1163],{"type":21,"tag":140,"props":1152,"children":1153},{},[1154],{"type":26,"value":1155},"就活は企業に選ばれるだけのイベントではありません",{"type":26,"value":1157},"。自分がどの企業に",{"type":21,"tag":140,"props":1159,"children":1160},{},[1161],{"type":26,"value":1162},"人生の時間を投資するか",{"type":26,"value":1164},"を決める意思決定です。そのために必要な情報を集め、自分なりの基準で判断する。それが「生殺与奪の権を他人に握らせない」就活だと思っています。",{"type":21,"tag":65,"props":1166,"children":1168},{"id":1167},"最後に",[1169],{"type":26,"value":1167},{"type":21,"tag":22,"props":1171,"children":1172},{},[1173],{"type":26,"value":1174},"ここまで色々と率直なことを書きましたが、今回の就活を通して関わってくださった企業の皆様には本当に感謝しています。",{"type":21,"tag":22,"props":1176,"children":1177},{},[1178],{"type":26,"value":1179},"面接や面談ではどの企業も対等かつ丁寧に時間をかけて対応してくださいました。特にリクルーターの方々には、キャリアについて真剣に向き合っていただき、多くの学びを得ることができました。",{"type":21,"tag":22,"props":1181,"children":1182},{},[1183],{"type":26,"value":1184},"この場を借りて改めて感謝申し上げます。\nそして、ここまで読んでいただきありがとうございました。\nこの記事が、これから就活を迎えるエンジニア志望の方の参考になれば幸いです。",{"title":8,"searchDepth":39,"depth":39,"links":1186},[1187,1188,1189,1190,1191,1192,1197,1202,1210,1214,1215],{"id":67,"depth":39,"text":67},{"id":86,"depth":39,"text":86},{"id":121,"depth":39,"text":121},{"id":131,"depth":39,"text":131},{"id":226,"depth":39,"text":226},{"id":333,"depth":39,"text":336,"children":1193},[1194,1196],{"id":352,"depth":1195,"text":352},3,{"id":362,"depth":1195,"text":365},{"id":461,"depth":39,"text":464,"children":1198},[1199,1200,1201],{"id":472,"depth":1195,"text":472},{"id":508,"depth":1195,"text":352},{"id":529,"depth":1195,"text":529},{"id":637,"depth":39,"text":640,"children":1203},[1204,1205,1206,1207,1208,1209],{"id":660,"depth":1195,"text":663},{"id":706,"depth":1195,"text":706},{"id":769,"depth":1195,"text":769},{"id":794,"depth":1195,"text":794},{"id":804,"depth":1195,"text":804},{"id":994,"depth":1195,"text":994},{"id":1023,"depth":39,"text":1026,"children":1211},[1212,1213],{"id":1037,"depth":1195,"text":1040},{"id":1048,"depth":1195,"text":1051},{"id":1109,"depth":39,"text":1109},{"id":1167,"depth":39,"text":1167},"content:articles:career:new-graduate-job-hunting.md","articles/career/new-graduate-job-hunting.md","articles/career/new-graduate-job-hunting",{"_path":1220,"_dir":1221,"_draft":7,"_partial":7,"_locale":8,"title":1222,"description":1223,"date":1224,"tags":1225,"rowTypeId":12,"sitemap":1231,"body":1232,"_type":41,"_id":4416,"_source":43,"_file":4417,"_stem":4418,"_extension":46},"/articles/tech/blazor/undo-redo-command-pattern","blazor","StackベースのUndo/RedoをC#で実装する｜CompositeCommandで複数操作も一括取り消し","楽譜エディタのUndo/RedoをCommandパターンで実装した実例を紹介します。2本のStackで履歴を管理し、CompositeCommandで複数操作を1手としてまとめ、TrimHistoryで履歴上限を設ける方法を解説します。","2026-04-17",[1226,1227,1228,1229,1230],"C#","デザインパターン","Command","Blazor","PICOM",{"loc":1220,"lastmod":1224,"priority":12},{"type":18,"children":1233,"toc":4407},[1234,1238,1243,1256,1286,1292,1297,1309,1315,1320,1576,1627,1632,2095,2120,2126,2153,3061,3066,3086,3121,3127,3132,3145,3436,3463,3529,3550,3556,3583,3588,4121,4133,4138,4332,4337,4342,4401],{"type":21,"tag":65,"props":1235,"children":1236},{"id":67},[1237],{"type":26,"value":67},{"type":21,"tag":22,"props":1239,"children":1240},{},[1241],{"type":26,"value":1242},"楽譜エディタ「PICOM」を作り始めて、Undo/Redoがないと不便だと感じ実装しました。おそらく、多くの編集系UIを持つアプリでは必須機能だと思います。",{"type":21,"tag":22,"props":1244,"children":1245},{},[1246,1248,1254],{"type":26,"value":1247},"そこで、この記事では、PICOMで実装したUndo/Redoの仕組みを紹介します。教科書通りの",{"type":21,"tag":670,"props":1249,"children":1251},{"content":1250},"実行する処理をオブジェクトとしてカプセル化し、履歴管理や取り消しを可能にするデザインパターン",[1252],{"type":26,"value":1253},"Commandパターン",{"type":26,"value":1255},"ですが、実際に使えるものにするには「複数操作を1手としてまとめたい」「履歴が無限に増えるのは困る」といった現実的な課題があります。そのあたりをどう解決したかを具体的に書きます。",{"type":21,"tag":76,"props":1257,"children":1258},{},[1259],{"type":21,"tag":22,"props":1260,"children":1261},{},[1262,1268,1270,1276,1278,1284],{"type":21,"tag":566,"props":1263,"children":1265},{"className":1264},[],[1266],{"type":26,"value":1267},"ICommand",{"type":26,"value":1269},"インターフェースを定義し、",{"type":21,"tag":566,"props":1271,"children":1273},{"className":1272},[],[1274],{"type":26,"value":1275},"UndoRedoManager",{"type":26,"value":1277},"が2本のStackで履歴を管理します。",{"type":21,"tag":566,"props":1279,"children":1281},{"className":1280},[],[1282],{"type":26,"value":1283},"CompositeCommand",{"type":26,"value":1285},"で複数のコマンドを1手にまとめ、ピアノロール上の複数音符を同時に移動するような操作も一発でUndoできます。履歴には上限を設けてメモリリークを防いでいます。",{"type":21,"tag":65,"props":1287,"children":1289},{"id":1288},"undoredoの仕組みを考える",[1290],{"type":26,"value":1291},"Undo/Redoの仕組みを考える",{"type":21,"tag":22,"props":1293,"children":1294},{},[1295],{"type":26,"value":1296},"Undo/Redoを愚直に実装する方法として、操作をするたびに状態を時系列に沿って記憶するという方法が思いつきます。しかし、この方法ではメモリ効率も悪く、状態管理も非常に複雑化します。",{"type":21,"tag":22,"props":1298,"children":1299},{},[1300,1302,1307],{"type":26,"value":1301},"そこで、デザインパターンであるCommandパターンを利用します。Commandパターンでは、状態ではなく ",{"type":21,"tag":140,"props":1303,"children":1304},{},[1305],{"type":26,"value":1306},"操作",{"type":26,"value":1308}," を記憶するため、以前の状態に復元したり、次の状態へ進めたりなどが容易に実現できます。\n続いて、実際の実装を見ていきましょう。",{"type":21,"tag":65,"props":1310,"children":1312},{"id":1311},"icommand最小インターフェースから始める",[1313],{"type":26,"value":1314},"ICommand：最小インターフェースから始める",{"type":21,"tag":22,"props":1316,"children":1317},{},[1318],{"type":26,"value":1319},"Commandパターンの出発点は「実行」と「取り消し」を1ペアで持つインターフェースです。PICOMでは次のように定義しています。",{"type":21,"tag":561,"props":1321,"children":1325},{"className":1322,"code":1323,"language":1324,"meta":8,"style":8},"language-csharp shiki shiki-themes vitesse-dark","public interface ICommand\n{\n    /// \u003Csummary>コマンドを実行する\u003C/summary>\n    void Execute();\n\n    /// \u003Csummary>コマンドを取り消す\u003C/summary>\n    void Undo();\n\n    /// \u003Csummary>コマンドの説明（デバッグ/UI表示用）\u003C/summary>\n    string Description { get; }\n}\n","csharp",[1326],{"type":21,"tag":566,"props":1327,"children":1328},{"__ignoreMap":8},[1329,1351,1360,1404,1424,1434,1471,1488,1496,1533,1567],{"type":21,"tag":1330,"props":1331,"children":1333},"span",{"class":1332,"line":12},"line",[1334,1340,1345],{"type":21,"tag":1330,"props":1335,"children":1337},{"style":1336},"--shiki-default:#CB7676",[1338],{"type":26,"value":1339},"public",{"type":21,"tag":1330,"props":1341,"children":1342},{"style":1336},[1343],{"type":26,"value":1344}," interface",{"type":21,"tag":1330,"props":1346,"children":1348},{"style":1347},"--shiki-default:#5DA994",[1349],{"type":26,"value":1350}," ICommand\n",{"type":21,"tag":1330,"props":1352,"children":1353},{"class":1332,"line":39},[1354],{"type":21,"tag":1330,"props":1355,"children":1357},{"style":1356},"--shiki-default:#666666",[1358],{"type":26,"value":1359},"{\n",{"type":21,"tag":1330,"props":1361,"children":1362},{"class":1332,"line":1195},[1363,1369,1374,1380,1385,1390,1395,1399],{"type":21,"tag":1330,"props":1364,"children":1366},{"style":1365},"--shiki-default:#758575DD",[1367],{"type":26,"value":1368},"    /// ",{"type":21,"tag":1330,"props":1370,"children":1371},{"style":1356},[1372],{"type":26,"value":1373},"\u003C",{"type":21,"tag":1330,"props":1375,"children":1377},{"style":1376},"--shiki-default:#4D9375",[1378],{"type":26,"value":1379},"summary",{"type":21,"tag":1330,"props":1381,"children":1382},{"style":1356},[1383],{"type":26,"value":1384},">",{"type":21,"tag":1330,"props":1386,"children":1387},{"style":1365},[1388],{"type":26,"value":1389},"コマンドを実行する",{"type":21,"tag":1330,"props":1391,"children":1392},{"style":1356},[1393],{"type":26,"value":1394},"\u003C/",{"type":21,"tag":1330,"props":1396,"children":1397},{"style":1376},[1398],{"type":26,"value":1379},{"type":21,"tag":1330,"props":1400,"children":1401},{"style":1356},[1402],{"type":26,"value":1403},">\n",{"type":21,"tag":1330,"props":1405,"children":1407},{"class":1332,"line":1406},4,[1408,1413,1419],{"type":21,"tag":1330,"props":1409,"children":1410},{"style":1376},[1411],{"type":26,"value":1412},"    void",{"type":21,"tag":1330,"props":1414,"children":1416},{"style":1415},"--shiki-default:#80A665",[1417],{"type":26,"value":1418}," Execute",{"type":21,"tag":1330,"props":1420,"children":1421},{"style":1356},[1422],{"type":26,"value":1423},"();\n",{"type":21,"tag":1330,"props":1425,"children":1427},{"class":1332,"line":1426},5,[1428],{"type":21,"tag":1330,"props":1429,"children":1431},{"emptyLinePlaceholder":1430},true,[1432],{"type":26,"value":1433},"\n",{"type":21,"tag":1330,"props":1435,"children":1437},{"class":1332,"line":1436},6,[1438,1442,1446,1450,1454,1459,1463,1467],{"type":21,"tag":1330,"props":1439,"children":1440},{"style":1365},[1441],{"type":26,"value":1368},{"type":21,"tag":1330,"props":1443,"children":1444},{"style":1356},[1445],{"type":26,"value":1373},{"type":21,"tag":1330,"props":1447,"children":1448},{"style":1376},[1449],{"type":26,"value":1379},{"type":21,"tag":1330,"props":1451,"children":1452},{"style":1356},[1453],{"type":26,"value":1384},{"type":21,"tag":1330,"props":1455,"children":1456},{"style":1365},[1457],{"type":26,"value":1458},"コマンドを取り消す",{"type":21,"tag":1330,"props":1460,"children":1461},{"style":1356},[1462],{"type":26,"value":1394},{"type":21,"tag":1330,"props":1464,"children":1465},{"style":1376},[1466],{"type":26,"value":1379},{"type":21,"tag":1330,"props":1468,"children":1469},{"style":1356},[1470],{"type":26,"value":1403},{"type":21,"tag":1330,"props":1472,"children":1474},{"class":1332,"line":1473},7,[1475,1479,1484],{"type":21,"tag":1330,"props":1476,"children":1477},{"style":1376},[1478],{"type":26,"value":1412},{"type":21,"tag":1330,"props":1480,"children":1481},{"style":1415},[1482],{"type":26,"value":1483}," Undo",{"type":21,"tag":1330,"props":1485,"children":1486},{"style":1356},[1487],{"type":26,"value":1423},{"type":21,"tag":1330,"props":1489,"children":1491},{"class":1332,"line":1490},8,[1492],{"type":21,"tag":1330,"props":1493,"children":1494},{"emptyLinePlaceholder":1430},[1495],{"type":26,"value":1433},{"type":21,"tag":1330,"props":1497,"children":1499},{"class":1332,"line":1498},9,[1500,1504,1508,1512,1516,1521,1525,1529],{"type":21,"tag":1330,"props":1501,"children":1502},{"style":1365},[1503],{"type":26,"value":1368},{"type":21,"tag":1330,"props":1505,"children":1506},{"style":1356},[1507],{"type":26,"value":1373},{"type":21,"tag":1330,"props":1509,"children":1510},{"style":1376},[1511],{"type":26,"value":1379},{"type":21,"tag":1330,"props":1513,"children":1514},{"style":1356},[1515],{"type":26,"value":1384},{"type":21,"tag":1330,"props":1517,"children":1518},{"style":1365},[1519],{"type":26,"value":1520},"コマンドの説明（デバッグ/UI表示用）",{"type":21,"tag":1330,"props":1522,"children":1523},{"style":1356},[1524],{"type":26,"value":1394},{"type":21,"tag":1330,"props":1526,"children":1527},{"style":1376},[1528],{"type":26,"value":1379},{"type":21,"tag":1330,"props":1530,"children":1531},{"style":1356},[1532],{"type":26,"value":1403},{"type":21,"tag":1330,"props":1534,"children":1536},{"class":1332,"line":1535},10,[1537,1542,1547,1552,1557,1562],{"type":21,"tag":1330,"props":1538,"children":1539},{"style":1376},[1540],{"type":26,"value":1541},"    string",{"type":21,"tag":1330,"props":1543,"children":1544},{"style":1415},[1545],{"type":26,"value":1546}," Description",{"type":21,"tag":1330,"props":1548,"children":1549},{"style":1356},[1550],{"type":26,"value":1551}," {",{"type":21,"tag":1330,"props":1553,"children":1554},{"style":1336},[1555],{"type":26,"value":1556}," get",{"type":21,"tag":1330,"props":1558,"children":1559},{"style":1356},[1560],{"type":26,"value":1561},";",{"type":21,"tag":1330,"props":1563,"children":1564},{"style":1356},[1565],{"type":26,"value":1566}," }\n",{"type":21,"tag":1330,"props":1568,"children":1570},{"class":1332,"line":1569},11,[1571],{"type":21,"tag":1330,"props":1572,"children":1573},{"style":1356},[1574],{"type":26,"value":1575},"}\n",{"type":21,"tag":22,"props":1577,"children":1578},{},[1579,1581,1587,1589,1595,1597,1602,1604,1610,1612,1618,1620,1625],{"type":26,"value":1580},"ポイントは",{"type":21,"tag":566,"props":1582,"children":1584},{"className":1583},[],[1585],{"type":26,"value":1586},"Description",{"type":26,"value":1588},"を入れていることです。履歴一覧を出したり、デバッグで",{"type":21,"tag":566,"props":1590,"children":1592},{"className":1591},[],[1593],{"type":26,"value":1594},"Console.WriteLine",{"type":26,"value":1596},"する時に、どのコマンドが何をしたのかを文字列で追えると",{"type":21,"tag":140,"props":1598,"children":1599},{},[1600],{"type":26,"value":1601},"デバッグの効率が雲泥の差",{"type":26,"value":1603},"です。.NET標準の",{"type":21,"tag":566,"props":1605,"children":1607},{"className":1606},[],[1608],{"type":26,"value":1609},"System.Windows.Input.ICommand",{"type":26,"value":1611},"とは別物なので、名前が被る場合は名前空間を明示するかリネームしてください。PICOMはBlazor WASMで実装しているため、",{"type":21,"tag":566,"props":1613,"children":1615},{"className":1614},[],[1616],{"type":26,"value":1617},"System.Windows",{"type":26,"value":1619},"の",{"type":21,"tag":566,"props":1621,"children":1623},{"className":1622},[],[1624],{"type":26,"value":1267},{"type":26,"value":1626},"と衝突することはなかったです。",{"type":21,"tag":22,"props":1628,"children":1629},{},[1630],{"type":26,"value":1631},"具体的なコマンドの実装例として、音符追加コマンドを載せておきます。",{"type":21,"tag":561,"props":1633,"children":1635},{"className":1322,"code":1634,"language":1324,"meta":8,"style":8},"public class AddNoteCommand : ICommand\n{\n    private readonly Track _track;\n    private readonly int _position128Note;\n    private readonly SoundComponentBase _component;\n\n    public string Description => $\"ノート追加 at {_position128Note}\";\n\n    public AddNoteCommand(Track track, int position128Note, SoundComponentBase component)\n    {\n        _track = track;\n        _position128Note = position128Note;\n        _component = component;\n    }\n\n    public void Execute() => _track.AddAt(_position128Note, _component);\n    public void Undo() => _track.RemoveAt(_position128Note, _component);\n}\n",[1636],{"type":21,"tag":566,"props":1637,"children":1638},{"__ignoreMap":8},[1639,1665,1672,1700,1725,1750,1757,1815,1822,1880,1888,1910,1931,1952,1961,1969,2030,2087],{"type":21,"tag":1330,"props":1640,"children":1641},{"class":1332,"line":12},[1642,1646,1651,1656,1661],{"type":21,"tag":1330,"props":1643,"children":1644},{"style":1336},[1645],{"type":26,"value":1339},{"type":21,"tag":1330,"props":1647,"children":1648},{"style":1336},[1649],{"type":26,"value":1650}," class",{"type":21,"tag":1330,"props":1652,"children":1653},{"style":1347},[1654],{"type":26,"value":1655}," AddNoteCommand",{"type":21,"tag":1330,"props":1657,"children":1658},{"style":1356},[1659],{"type":26,"value":1660}," :",{"type":21,"tag":1330,"props":1662,"children":1663},{"style":1347},[1664],{"type":26,"value":1350},{"type":21,"tag":1330,"props":1666,"children":1667},{"class":1332,"line":39},[1668],{"type":21,"tag":1330,"props":1669,"children":1670},{"style":1356},[1671],{"type":26,"value":1359},{"type":21,"tag":1330,"props":1673,"children":1674},{"class":1332,"line":1195},[1675,1680,1685,1690,1695],{"type":21,"tag":1330,"props":1676,"children":1677},{"style":1336},[1678],{"type":26,"value":1679},"    private",{"type":21,"tag":1330,"props":1681,"children":1682},{"style":1336},[1683],{"type":26,"value":1684}," readonly",{"type":21,"tag":1330,"props":1686,"children":1687},{"style":1347},[1688],{"type":26,"value":1689}," Track",{"type":21,"tag":1330,"props":1691,"children":1692},{"style":1415},[1693],{"type":26,"value":1694}," _track",{"type":21,"tag":1330,"props":1696,"children":1697},{"style":1356},[1698],{"type":26,"value":1699},";\n",{"type":21,"tag":1330,"props":1701,"children":1702},{"class":1332,"line":1406},[1703,1707,1711,1716,1721],{"type":21,"tag":1330,"props":1704,"children":1705},{"style":1336},[1706],{"type":26,"value":1679},{"type":21,"tag":1330,"props":1708,"children":1709},{"style":1336},[1710],{"type":26,"value":1684},{"type":21,"tag":1330,"props":1712,"children":1713},{"style":1376},[1714],{"type":26,"value":1715}," int",{"type":21,"tag":1330,"props":1717,"children":1718},{"style":1415},[1719],{"type":26,"value":1720}," _position128Note",{"type":21,"tag":1330,"props":1722,"children":1723},{"style":1356},[1724],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1726,"children":1727},{"class":1332,"line":1426},[1728,1732,1736,1741,1746],{"type":21,"tag":1330,"props":1729,"children":1730},{"style":1336},[1731],{"type":26,"value":1679},{"type":21,"tag":1330,"props":1733,"children":1734},{"style":1336},[1735],{"type":26,"value":1684},{"type":21,"tag":1330,"props":1737,"children":1738},{"style":1347},[1739],{"type":26,"value":1740}," SoundComponentBase",{"type":21,"tag":1330,"props":1742,"children":1743},{"style":1415},[1744],{"type":26,"value":1745}," _component",{"type":21,"tag":1330,"props":1747,"children":1748},{"style":1356},[1749],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1751,"children":1752},{"class":1332,"line":1436},[1753],{"type":21,"tag":1330,"props":1754,"children":1755},{"emptyLinePlaceholder":1430},[1756],{"type":26,"value":1433},{"type":21,"tag":1330,"props":1758,"children":1759},{"class":1332,"line":1473},[1760,1765,1770,1774,1779,1785,1791,1796,1801,1806,1811],{"type":21,"tag":1330,"props":1761,"children":1762},{"style":1336},[1763],{"type":26,"value":1764},"    public",{"type":21,"tag":1330,"props":1766,"children":1767},{"style":1376},[1768],{"type":26,"value":1769}," string",{"type":21,"tag":1330,"props":1771,"children":1772},{"style":1415},[1773],{"type":26,"value":1546},{"type":21,"tag":1330,"props":1775,"children":1776},{"style":1336},[1777],{"type":26,"value":1778}," =>",{"type":21,"tag":1330,"props":1780,"children":1782},{"style":1781},"--shiki-default:#C98A7D77",[1783],{"type":26,"value":1784}," $\"",{"type":21,"tag":1330,"props":1786,"children":1788},{"style":1787},"--shiki-default:#C98A7D",[1789],{"type":26,"value":1790},"ノート追加 at ",{"type":21,"tag":1330,"props":1792,"children":1793},{"style":1356},[1794],{"type":26,"value":1795},"{",{"type":21,"tag":1330,"props":1797,"children":1798},{"style":1787},[1799],{"type":26,"value":1800},"_position128Note",{"type":21,"tag":1330,"props":1802,"children":1803},{"style":1356},[1804],{"type":26,"value":1805},"}",{"type":21,"tag":1330,"props":1807,"children":1808},{"style":1781},[1809],{"type":26,"value":1810},"\"",{"type":21,"tag":1330,"props":1812,"children":1813},{"style":1356},[1814],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1816,"children":1817},{"class":1332,"line":1490},[1818],{"type":21,"tag":1330,"props":1819,"children":1820},{"emptyLinePlaceholder":1430},[1821],{"type":26,"value":1433},{"type":21,"tag":1330,"props":1823,"children":1824},{"class":1332,"line":1498},[1825,1829,1833,1838,1843,1848,1853,1857,1862,1866,1870,1875],{"type":21,"tag":1330,"props":1826,"children":1827},{"style":1336},[1828],{"type":26,"value":1764},{"type":21,"tag":1330,"props":1830,"children":1831},{"style":1415},[1832],{"type":26,"value":1655},{"type":21,"tag":1330,"props":1834,"children":1835},{"style":1356},[1836],{"type":26,"value":1837},"(",{"type":21,"tag":1330,"props":1839,"children":1840},{"style":1347},[1841],{"type":26,"value":1842},"Track",{"type":21,"tag":1330,"props":1844,"children":1845},{"style":1415},[1846],{"type":26,"value":1847}," track",{"type":21,"tag":1330,"props":1849,"children":1850},{"style":1356},[1851],{"type":26,"value":1852},",",{"type":21,"tag":1330,"props":1854,"children":1855},{"style":1376},[1856],{"type":26,"value":1715},{"type":21,"tag":1330,"props":1858,"children":1859},{"style":1415},[1860],{"type":26,"value":1861}," position128Note",{"type":21,"tag":1330,"props":1863,"children":1864},{"style":1356},[1865],{"type":26,"value":1852},{"type":21,"tag":1330,"props":1867,"children":1868},{"style":1347},[1869],{"type":26,"value":1740},{"type":21,"tag":1330,"props":1871,"children":1872},{"style":1415},[1873],{"type":26,"value":1874}," component",{"type":21,"tag":1330,"props":1876,"children":1877},{"style":1356},[1878],{"type":26,"value":1879},")\n",{"type":21,"tag":1330,"props":1881,"children":1882},{"class":1332,"line":1535},[1883],{"type":21,"tag":1330,"props":1884,"children":1885},{"style":1356},[1886],{"type":26,"value":1887},"    {\n",{"type":21,"tag":1330,"props":1889,"children":1890},{"class":1332,"line":1569},[1891,1897,1902,1906],{"type":21,"tag":1330,"props":1892,"children":1894},{"style":1893},"--shiki-default:#BD976A",[1895],{"type":26,"value":1896},"        _track",{"type":21,"tag":1330,"props":1898,"children":1899},{"style":1356},[1900],{"type":26,"value":1901}," =",{"type":21,"tag":1330,"props":1903,"children":1904},{"style":1893},[1905],{"type":26,"value":1847},{"type":21,"tag":1330,"props":1907,"children":1908},{"style":1356},[1909],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1911,"children":1913},{"class":1332,"line":1912},12,[1914,1919,1923,1927],{"type":21,"tag":1330,"props":1915,"children":1916},{"style":1893},[1917],{"type":26,"value":1918},"        _position128Note",{"type":21,"tag":1330,"props":1920,"children":1921},{"style":1356},[1922],{"type":26,"value":1901},{"type":21,"tag":1330,"props":1924,"children":1925},{"style":1893},[1926],{"type":26,"value":1861},{"type":21,"tag":1330,"props":1928,"children":1929},{"style":1356},[1930],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1932,"children":1934},{"class":1332,"line":1933},13,[1935,1940,1944,1948],{"type":21,"tag":1330,"props":1936,"children":1937},{"style":1893},[1938],{"type":26,"value":1939},"        _component",{"type":21,"tag":1330,"props":1941,"children":1942},{"style":1356},[1943],{"type":26,"value":1901},{"type":21,"tag":1330,"props":1945,"children":1946},{"style":1893},[1947],{"type":26,"value":1874},{"type":21,"tag":1330,"props":1949,"children":1950},{"style":1356},[1951],{"type":26,"value":1699},{"type":21,"tag":1330,"props":1953,"children":1955},{"class":1332,"line":1954},14,[1956],{"type":21,"tag":1330,"props":1957,"children":1958},{"style":1356},[1959],{"type":26,"value":1960},"    }\n",{"type":21,"tag":1330,"props":1962,"children":1964},{"class":1332,"line":1963},15,[1965],{"type":21,"tag":1330,"props":1966,"children":1967},{"emptyLinePlaceholder":1430},[1968],{"type":26,"value":1433},{"type":21,"tag":1330,"props":1970,"children":1972},{"class":1332,"line":1971},16,[1973,1977,1982,1986,1991,1995,1999,2004,2009,2013,2017,2021,2025],{"type":21,"tag":1330,"props":1974,"children":1975},{"style":1336},[1976],{"type":26,"value":1764},{"type":21,"tag":1330,"props":1978,"children":1979},{"style":1376},[1980],{"type":26,"value":1981}," void",{"type":21,"tag":1330,"props":1983,"children":1984},{"style":1415},[1985],{"type":26,"value":1418},{"type":21,"tag":1330,"props":1987,"children":1988},{"style":1356},[1989],{"type":26,"value":1990},"()",{"type":21,"tag":1330,"props":1992,"children":1993},{"style":1336},[1994],{"type":26,"value":1778},{"type":21,"tag":1330,"props":1996,"children":1997},{"style":1893},[1998],{"type":26,"value":1694},{"type":21,"tag":1330,"props":2000,"children":2001},{"style":1356},[2002],{"type":26,"value":2003},".",{"type":21,"tag":1330,"props":2005,"children":2006},{"style":1415},[2007],{"type":26,"value":2008},"AddAt",{"type":21,"tag":1330,"props":2010,"children":2011},{"style":1356},[2012],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2014,"children":2015},{"style":1893},[2016],{"type":26,"value":1800},{"type":21,"tag":1330,"props":2018,"children":2019},{"style":1356},[2020],{"type":26,"value":1852},{"type":21,"tag":1330,"props":2022,"children":2023},{"style":1893},[2024],{"type":26,"value":1745},{"type":21,"tag":1330,"props":2026,"children":2027},{"style":1356},[2028],{"type":26,"value":2029},");\n",{"type":21,"tag":1330,"props":2031,"children":2033},{"class":1332,"line":2032},17,[2034,2038,2042,2046,2050,2054,2058,2062,2067,2071,2075,2079,2083],{"type":21,"tag":1330,"props":2035,"children":2036},{"style":1336},[2037],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2039,"children":2040},{"style":1376},[2041],{"type":26,"value":1981},{"type":21,"tag":1330,"props":2043,"children":2044},{"style":1415},[2045],{"type":26,"value":1483},{"type":21,"tag":1330,"props":2047,"children":2048},{"style":1356},[2049],{"type":26,"value":1990},{"type":21,"tag":1330,"props":2051,"children":2052},{"style":1336},[2053],{"type":26,"value":1778},{"type":21,"tag":1330,"props":2055,"children":2056},{"style":1893},[2057],{"type":26,"value":1694},{"type":21,"tag":1330,"props":2059,"children":2060},{"style":1356},[2061],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2063,"children":2064},{"style":1415},[2065],{"type":26,"value":2066},"RemoveAt",{"type":21,"tag":1330,"props":2068,"children":2069},{"style":1356},[2070],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2072,"children":2073},{"style":1893},[2074],{"type":26,"value":1800},{"type":21,"tag":1330,"props":2076,"children":2077},{"style":1356},[2078],{"type":26,"value":1852},{"type":21,"tag":1330,"props":2080,"children":2081},{"style":1893},[2082],{"type":26,"value":1745},{"type":21,"tag":1330,"props":2084,"children":2085},{"style":1356},[2086],{"type":26,"value":2029},{"type":21,"tag":1330,"props":2088,"children":2090},{"class":1332,"line":2089},18,[2091],{"type":21,"tag":1330,"props":2092,"children":2093},{"style":1356},[2094],{"type":26,"value":1575},{"type":21,"tag":22,"props":2096,"children":2097},{},[2098,2104,2105,2111,2113,2118],{"type":21,"tag":566,"props":2099,"children":2101},{"className":2100},[],[2102],{"type":26,"value":2103},"Execute",{"type":26,"value":1123},{"type":21,"tag":566,"props":2106,"children":2108},{"className":2107},[],[2109],{"type":26,"value":2110},"Undo",{"type":26,"value":2112},"が対称になっているのがCommandパターンの気持ちよさです。",{"type":21,"tag":566,"props":2114,"children":2116},{"className":2115},[],[2117],{"type":26,"value":2110},{"type":26,"value":2119},"で失敗するような操作は原則として作らない、というのが運用上の鉄則です。",{"type":21,"tag":65,"props":2121,"children":2123},{"id":2122},"undoredomanager2本のstackで履歴を管理",[2124],{"type":26,"value":2125},"UndoRedoManager：2本のStackで履歴を管理",{"type":21,"tag":22,"props":2127,"children":2128},{},[2129,2131,2136,2138,2144,2145,2151],{"type":26,"value":2130},"履歴の管理本体は",{"type":21,"tag":566,"props":2132,"children":2134},{"className":2133},[],[2135],{"type":26,"value":1275},{"type":26,"value":2137},"です。",{"type":21,"tag":566,"props":2139,"children":2141},{"className":2140},[],[2142],{"type":26,"value":2143},"_undoStack",{"type":26,"value":1123},{"type":21,"tag":566,"props":2146,"children":2148},{"className":2147},[],[2149],{"type":26,"value":2150},"_redoStack",{"type":26,"value":2152},"の2本で押したり引いたりします。",{"type":21,"tag":561,"props":2154,"children":2156},{"className":1322,"code":2155,"language":1324,"meta":8,"style":8},"public class UndoRedoManager\n{\n    private readonly Stack\u003CICommand> _undoStack = new();\n    private readonly Stack\u003CICommand> _redoStack = new();\n    private readonly int _maxHistorySize;\n\n    public event Action? StateChanged;\n\n    public UndoRedoManager(int maxHistorySize = 100)\n    {\n        _maxHistorySize = maxHistorySize;\n    }\n\n    public bool CanUndo => _undoStack.Count > 0;\n    public bool CanRedo => _redoStack.Count > 0;\n\n    public void Execute(ICommand command)\n    {\n        command.Execute();\n        _undoStack.Push(command);\n        _redoStack.Clear();\n        TrimHistory();\n        StateChanged?.Invoke();\n    }\n\n    public void Undo()\n    {\n        if (!CanUndo) return;\n        var command = _undoStack.Pop();\n        command.Undo();\n        _redoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n\n    public void Redo()\n    {\n        if (!CanRedo) return;\n        var command = _redoStack.Pop();\n        command.Execute();\n        _undoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n}\n",[2157],{"type":21,"tag":566,"props":2158,"children":2159},{"__ignoreMap":8},[2160,2176,2183,2229,2273,2297,2304,2335,2342,2382,2389,2409,2416,2423,2471,2515,2522,2554,2561,2582,2613,2635,2648,2674,2682,2690,2711,2719,2757,2791,2811,2839,2863,2871,2879,2900,2908,2941,2973,2993,3021,3045,3053],{"type":21,"tag":1330,"props":2161,"children":2162},{"class":1332,"line":12},[2163,2167,2171],{"type":21,"tag":1330,"props":2164,"children":2165},{"style":1336},[2166],{"type":26,"value":1339},{"type":21,"tag":1330,"props":2168,"children":2169},{"style":1336},[2170],{"type":26,"value":1650},{"type":21,"tag":1330,"props":2172,"children":2173},{"style":1347},[2174],{"type":26,"value":2175}," UndoRedoManager\n",{"type":21,"tag":1330,"props":2177,"children":2178},{"class":1332,"line":39},[2179],{"type":21,"tag":1330,"props":2180,"children":2181},{"style":1356},[2182],{"type":26,"value":1359},{"type":21,"tag":1330,"props":2184,"children":2185},{"class":1332,"line":1195},[2186,2190,2194,2199,2203,2207,2211,2216,2220,2225],{"type":21,"tag":1330,"props":2187,"children":2188},{"style":1336},[2189],{"type":26,"value":1679},{"type":21,"tag":1330,"props":2191,"children":2192},{"style":1336},[2193],{"type":26,"value":1684},{"type":21,"tag":1330,"props":2195,"children":2196},{"style":1347},[2197],{"type":26,"value":2198}," Stack",{"type":21,"tag":1330,"props":2200,"children":2201},{"style":1356},[2202],{"type":26,"value":1373},{"type":21,"tag":1330,"props":2204,"children":2205},{"style":1347},[2206],{"type":26,"value":1267},{"type":21,"tag":1330,"props":2208,"children":2209},{"style":1356},[2210],{"type":26,"value":1384},{"type":21,"tag":1330,"props":2212,"children":2213},{"style":1415},[2214],{"type":26,"value":2215}," _undoStack",{"type":21,"tag":1330,"props":2217,"children":2218},{"style":1356},[2219],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2221,"children":2222},{"style":1336},[2223],{"type":26,"value":2224}," new",{"type":21,"tag":1330,"props":2226,"children":2227},{"style":1356},[2228],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2230,"children":2231},{"class":1332,"line":1406},[2232,2236,2240,2244,2248,2252,2256,2261,2265,2269],{"type":21,"tag":1330,"props":2233,"children":2234},{"style":1336},[2235],{"type":26,"value":1679},{"type":21,"tag":1330,"props":2237,"children":2238},{"style":1336},[2239],{"type":26,"value":1684},{"type":21,"tag":1330,"props":2241,"children":2242},{"style":1347},[2243],{"type":26,"value":2198},{"type":21,"tag":1330,"props":2245,"children":2246},{"style":1356},[2247],{"type":26,"value":1373},{"type":21,"tag":1330,"props":2249,"children":2250},{"style":1347},[2251],{"type":26,"value":1267},{"type":21,"tag":1330,"props":2253,"children":2254},{"style":1356},[2255],{"type":26,"value":1384},{"type":21,"tag":1330,"props":2257,"children":2258},{"style":1415},[2259],{"type":26,"value":2260}," _redoStack",{"type":21,"tag":1330,"props":2262,"children":2263},{"style":1356},[2264],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2266,"children":2267},{"style":1336},[2268],{"type":26,"value":2224},{"type":21,"tag":1330,"props":2270,"children":2271},{"style":1356},[2272],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2274,"children":2275},{"class":1332,"line":1426},[2276,2280,2284,2288,2293],{"type":21,"tag":1330,"props":2277,"children":2278},{"style":1336},[2279],{"type":26,"value":1679},{"type":21,"tag":1330,"props":2281,"children":2282},{"style":1336},[2283],{"type":26,"value":1684},{"type":21,"tag":1330,"props":2285,"children":2286},{"style":1376},[2287],{"type":26,"value":1715},{"type":21,"tag":1330,"props":2289,"children":2290},{"style":1415},[2291],{"type":26,"value":2292}," _maxHistorySize",{"type":21,"tag":1330,"props":2294,"children":2295},{"style":1356},[2296],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2298,"children":2299},{"class":1332,"line":1436},[2300],{"type":21,"tag":1330,"props":2301,"children":2302},{"emptyLinePlaceholder":1430},[2303],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2305,"children":2306},{"class":1332,"line":1473},[2307,2311,2316,2321,2326,2331],{"type":21,"tag":1330,"props":2308,"children":2309},{"style":1336},[2310],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2312,"children":2313},{"style":1336},[2314],{"type":26,"value":2315}," event",{"type":21,"tag":1330,"props":2317,"children":2318},{"style":1347},[2319],{"type":26,"value":2320}," Action",{"type":21,"tag":1330,"props":2322,"children":2323},{"style":1356},[2324],{"type":26,"value":2325},"?",{"type":21,"tag":1330,"props":2327,"children":2328},{"style":1415},[2329],{"type":26,"value":2330}," StateChanged",{"type":21,"tag":1330,"props":2332,"children":2333},{"style":1356},[2334],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2336,"children":2337},{"class":1332,"line":1490},[2338],{"type":21,"tag":1330,"props":2339,"children":2340},{"emptyLinePlaceholder":1430},[2341],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2343,"children":2344},{"class":1332,"line":1498},[2345,2349,2354,2358,2363,2368,2372,2378],{"type":21,"tag":1330,"props":2346,"children":2347},{"style":1336},[2348],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2350,"children":2351},{"style":1415},[2352],{"type":26,"value":2353}," UndoRedoManager",{"type":21,"tag":1330,"props":2355,"children":2356},{"style":1356},[2357],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2359,"children":2360},{"style":1376},[2361],{"type":26,"value":2362},"int",{"type":21,"tag":1330,"props":2364,"children":2365},{"style":1415},[2366],{"type":26,"value":2367}," maxHistorySize",{"type":21,"tag":1330,"props":2369,"children":2370},{"style":1356},[2371],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2373,"children":2375},{"style":2374},"--shiki-default:#4C9A91",[2376],{"type":26,"value":2377}," 100",{"type":21,"tag":1330,"props":2379,"children":2380},{"style":1356},[2381],{"type":26,"value":1879},{"type":21,"tag":1330,"props":2383,"children":2384},{"class":1332,"line":1535},[2385],{"type":21,"tag":1330,"props":2386,"children":2387},{"style":1356},[2388],{"type":26,"value":1887},{"type":21,"tag":1330,"props":2390,"children":2391},{"class":1332,"line":1569},[2392,2397,2401,2405],{"type":21,"tag":1330,"props":2393,"children":2394},{"style":1893},[2395],{"type":26,"value":2396},"        _maxHistorySize",{"type":21,"tag":1330,"props":2398,"children":2399},{"style":1356},[2400],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2402,"children":2403},{"style":1893},[2404],{"type":26,"value":2367},{"type":21,"tag":1330,"props":2406,"children":2407},{"style":1356},[2408],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2410,"children":2411},{"class":1332,"line":1912},[2412],{"type":21,"tag":1330,"props":2413,"children":2414},{"style":1356},[2415],{"type":26,"value":1960},{"type":21,"tag":1330,"props":2417,"children":2418},{"class":1332,"line":1933},[2419],{"type":21,"tag":1330,"props":2420,"children":2421},{"emptyLinePlaceholder":1430},[2422],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2424,"children":2425},{"class":1332,"line":1954},[2426,2430,2435,2440,2444,2448,2452,2457,2462,2467],{"type":21,"tag":1330,"props":2427,"children":2428},{"style":1336},[2429],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2431,"children":2432},{"style":1376},[2433],{"type":26,"value":2434}," bool",{"type":21,"tag":1330,"props":2436,"children":2437},{"style":1415},[2438],{"type":26,"value":2439}," CanUndo",{"type":21,"tag":1330,"props":2441,"children":2442},{"style":1336},[2443],{"type":26,"value":1778},{"type":21,"tag":1330,"props":2445,"children":2446},{"style":1893},[2447],{"type":26,"value":2215},{"type":21,"tag":1330,"props":2449,"children":2450},{"style":1356},[2451],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2453,"children":2454},{"style":1893},[2455],{"type":26,"value":2456},"Count",{"type":21,"tag":1330,"props":2458,"children":2459},{"style":1356},[2460],{"type":26,"value":2461}," >",{"type":21,"tag":1330,"props":2463,"children":2464},{"style":2374},[2465],{"type":26,"value":2466}," 0",{"type":21,"tag":1330,"props":2468,"children":2469},{"style":1356},[2470],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2472,"children":2473},{"class":1332,"line":1963},[2474,2478,2482,2487,2491,2495,2499,2503,2507,2511],{"type":21,"tag":1330,"props":2475,"children":2476},{"style":1336},[2477],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2479,"children":2480},{"style":1376},[2481],{"type":26,"value":2434},{"type":21,"tag":1330,"props":2483,"children":2484},{"style":1415},[2485],{"type":26,"value":2486}," CanRedo",{"type":21,"tag":1330,"props":2488,"children":2489},{"style":1336},[2490],{"type":26,"value":1778},{"type":21,"tag":1330,"props":2492,"children":2493},{"style":1893},[2494],{"type":26,"value":2260},{"type":21,"tag":1330,"props":2496,"children":2497},{"style":1356},[2498],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2500,"children":2501},{"style":1893},[2502],{"type":26,"value":2456},{"type":21,"tag":1330,"props":2504,"children":2505},{"style":1356},[2506],{"type":26,"value":2461},{"type":21,"tag":1330,"props":2508,"children":2509},{"style":2374},[2510],{"type":26,"value":2466},{"type":21,"tag":1330,"props":2512,"children":2513},{"style":1356},[2514],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2516,"children":2517},{"class":1332,"line":1971},[2518],{"type":21,"tag":1330,"props":2519,"children":2520},{"emptyLinePlaceholder":1430},[2521],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2523,"children":2524},{"class":1332,"line":2032},[2525,2529,2533,2537,2541,2545,2550],{"type":21,"tag":1330,"props":2526,"children":2527},{"style":1336},[2528],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2530,"children":2531},{"style":1376},[2532],{"type":26,"value":1981},{"type":21,"tag":1330,"props":2534,"children":2535},{"style":1415},[2536],{"type":26,"value":1418},{"type":21,"tag":1330,"props":2538,"children":2539},{"style":1356},[2540],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2542,"children":2543},{"style":1347},[2544],{"type":26,"value":1267},{"type":21,"tag":1330,"props":2546,"children":2547},{"style":1415},[2548],{"type":26,"value":2549}," command",{"type":21,"tag":1330,"props":2551,"children":2552},{"style":1356},[2553],{"type":26,"value":1879},{"type":21,"tag":1330,"props":2555,"children":2556},{"class":1332,"line":2089},[2557],{"type":21,"tag":1330,"props":2558,"children":2559},{"style":1356},[2560],{"type":26,"value":1887},{"type":21,"tag":1330,"props":2562,"children":2564},{"class":1332,"line":2563},19,[2565,2570,2574,2578],{"type":21,"tag":1330,"props":2566,"children":2567},{"style":1893},[2568],{"type":26,"value":2569},"        command",{"type":21,"tag":1330,"props":2571,"children":2572},{"style":1356},[2573],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2575,"children":2576},{"style":1415},[2577],{"type":26,"value":2103},{"type":21,"tag":1330,"props":2579,"children":2580},{"style":1356},[2581],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2583,"children":2585},{"class":1332,"line":2584},20,[2586,2591,2595,2600,2604,2609],{"type":21,"tag":1330,"props":2587,"children":2588},{"style":1893},[2589],{"type":26,"value":2590},"        _undoStack",{"type":21,"tag":1330,"props":2592,"children":2593},{"style":1356},[2594],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2596,"children":2597},{"style":1415},[2598],{"type":26,"value":2599},"Push",{"type":21,"tag":1330,"props":2601,"children":2602},{"style":1356},[2603],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2605,"children":2606},{"style":1893},[2607],{"type":26,"value":2608},"command",{"type":21,"tag":1330,"props":2610,"children":2611},{"style":1356},[2612],{"type":26,"value":2029},{"type":21,"tag":1330,"props":2614,"children":2616},{"class":1332,"line":2615},21,[2617,2622,2626,2631],{"type":21,"tag":1330,"props":2618,"children":2619},{"style":1893},[2620],{"type":26,"value":2621},"        _redoStack",{"type":21,"tag":1330,"props":2623,"children":2624},{"style":1356},[2625],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2627,"children":2628},{"style":1415},[2629],{"type":26,"value":2630},"Clear",{"type":21,"tag":1330,"props":2632,"children":2633},{"style":1356},[2634],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2636,"children":2638},{"class":1332,"line":2637},22,[2639,2644],{"type":21,"tag":1330,"props":2640,"children":2641},{"style":1415},[2642],{"type":26,"value":2643},"        TrimHistory",{"type":21,"tag":1330,"props":2645,"children":2646},{"style":1356},[2647],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2649,"children":2651},{"class":1332,"line":2650},23,[2652,2657,2661,2665,2670],{"type":21,"tag":1330,"props":2653,"children":2654},{"style":1893},[2655],{"type":26,"value":2656},"        StateChanged",{"type":21,"tag":1330,"props":2658,"children":2659},{"style":1336},[2660],{"type":26,"value":2325},{"type":21,"tag":1330,"props":2662,"children":2663},{"style":1356},[2664],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2666,"children":2667},{"style":1415},[2668],{"type":26,"value":2669},"Invoke",{"type":21,"tag":1330,"props":2671,"children":2672},{"style":1356},[2673],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2675,"children":2677},{"class":1332,"line":2676},24,[2678],{"type":21,"tag":1330,"props":2679,"children":2680},{"style":1356},[2681],{"type":26,"value":1960},{"type":21,"tag":1330,"props":2683,"children":2685},{"class":1332,"line":2684},25,[2686],{"type":21,"tag":1330,"props":2687,"children":2688},{"emptyLinePlaceholder":1430},[2689],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2691,"children":2693},{"class":1332,"line":2692},26,[2694,2698,2702,2706],{"type":21,"tag":1330,"props":2695,"children":2696},{"style":1336},[2697],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2699,"children":2700},{"style":1376},[2701],{"type":26,"value":1981},{"type":21,"tag":1330,"props":2703,"children":2704},{"style":1415},[2705],{"type":26,"value":1483},{"type":21,"tag":1330,"props":2707,"children":2708},{"style":1356},[2709],{"type":26,"value":2710},"()\n",{"type":21,"tag":1330,"props":2712,"children":2714},{"class":1332,"line":2713},27,[2715],{"type":21,"tag":1330,"props":2716,"children":2717},{"style":1356},[2718],{"type":26,"value":1887},{"type":21,"tag":1330,"props":2720,"children":2722},{"class":1332,"line":2721},28,[2723,2728,2733,2738,2743,2748,2753],{"type":21,"tag":1330,"props":2724,"children":2725},{"style":1376},[2726],{"type":26,"value":2727},"        if",{"type":21,"tag":1330,"props":2729,"children":2730},{"style":1356},[2731],{"type":26,"value":2732}," (",{"type":21,"tag":1330,"props":2734,"children":2735},{"style":1336},[2736],{"type":26,"value":2737},"!",{"type":21,"tag":1330,"props":2739,"children":2740},{"style":1893},[2741],{"type":26,"value":2742},"CanUndo",{"type":21,"tag":1330,"props":2744,"children":2745},{"style":1356},[2746],{"type":26,"value":2747},")",{"type":21,"tag":1330,"props":2749,"children":2750},{"style":1376},[2751],{"type":26,"value":2752}," return",{"type":21,"tag":1330,"props":2754,"children":2755},{"style":1356},[2756],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2758,"children":2760},{"class":1332,"line":2759},29,[2761,2766,2770,2774,2778,2782,2787],{"type":21,"tag":1330,"props":2762,"children":2763},{"style":1336},[2764],{"type":26,"value":2765},"        var",{"type":21,"tag":1330,"props":2767,"children":2768},{"style":1415},[2769],{"type":26,"value":2549},{"type":21,"tag":1330,"props":2771,"children":2772},{"style":1356},[2773],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2775,"children":2776},{"style":1893},[2777],{"type":26,"value":2215},{"type":21,"tag":1330,"props":2779,"children":2780},{"style":1356},[2781],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2783,"children":2784},{"style":1415},[2785],{"type":26,"value":2786},"Pop",{"type":21,"tag":1330,"props":2788,"children":2789},{"style":1356},[2790],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2792,"children":2794},{"class":1332,"line":2793},30,[2795,2799,2803,2807],{"type":21,"tag":1330,"props":2796,"children":2797},{"style":1893},[2798],{"type":26,"value":2569},{"type":21,"tag":1330,"props":2800,"children":2801},{"style":1356},[2802],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2804,"children":2805},{"style":1415},[2806],{"type":26,"value":2110},{"type":21,"tag":1330,"props":2808,"children":2809},{"style":1356},[2810],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2812,"children":2814},{"class":1332,"line":2813},31,[2815,2819,2823,2827,2831,2835],{"type":21,"tag":1330,"props":2816,"children":2817},{"style":1893},[2818],{"type":26,"value":2621},{"type":21,"tag":1330,"props":2820,"children":2821},{"style":1356},[2822],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2824,"children":2825},{"style":1415},[2826],{"type":26,"value":2599},{"type":21,"tag":1330,"props":2828,"children":2829},{"style":1356},[2830],{"type":26,"value":1837},{"type":21,"tag":1330,"props":2832,"children":2833},{"style":1893},[2834],{"type":26,"value":2608},{"type":21,"tag":1330,"props":2836,"children":2837},{"style":1356},[2838],{"type":26,"value":2029},{"type":21,"tag":1330,"props":2840,"children":2842},{"class":1332,"line":2841},32,[2843,2847,2851,2855,2859],{"type":21,"tag":1330,"props":2844,"children":2845},{"style":1893},[2846],{"type":26,"value":2656},{"type":21,"tag":1330,"props":2848,"children":2849},{"style":1336},[2850],{"type":26,"value":2325},{"type":21,"tag":1330,"props":2852,"children":2853},{"style":1356},[2854],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2856,"children":2857},{"style":1415},[2858],{"type":26,"value":2669},{"type":21,"tag":1330,"props":2860,"children":2861},{"style":1356},[2862],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2864,"children":2866},{"class":1332,"line":2865},33,[2867],{"type":21,"tag":1330,"props":2868,"children":2869},{"style":1356},[2870],{"type":26,"value":1960},{"type":21,"tag":1330,"props":2872,"children":2874},{"class":1332,"line":2873},34,[2875],{"type":21,"tag":1330,"props":2876,"children":2877},{"emptyLinePlaceholder":1430},[2878],{"type":26,"value":1433},{"type":21,"tag":1330,"props":2880,"children":2882},{"class":1332,"line":2881},35,[2883,2887,2891,2896],{"type":21,"tag":1330,"props":2884,"children":2885},{"style":1336},[2886],{"type":26,"value":1764},{"type":21,"tag":1330,"props":2888,"children":2889},{"style":1376},[2890],{"type":26,"value":1981},{"type":21,"tag":1330,"props":2892,"children":2893},{"style":1415},[2894],{"type":26,"value":2895}," Redo",{"type":21,"tag":1330,"props":2897,"children":2898},{"style":1356},[2899],{"type":26,"value":2710},{"type":21,"tag":1330,"props":2901,"children":2903},{"class":1332,"line":2902},36,[2904],{"type":21,"tag":1330,"props":2905,"children":2906},{"style":1356},[2907],{"type":26,"value":1887},{"type":21,"tag":1330,"props":2909,"children":2911},{"class":1332,"line":2910},37,[2912,2916,2920,2924,2929,2933,2937],{"type":21,"tag":1330,"props":2913,"children":2914},{"style":1376},[2915],{"type":26,"value":2727},{"type":21,"tag":1330,"props":2917,"children":2918},{"style":1356},[2919],{"type":26,"value":2732},{"type":21,"tag":1330,"props":2921,"children":2922},{"style":1336},[2923],{"type":26,"value":2737},{"type":21,"tag":1330,"props":2925,"children":2926},{"style":1893},[2927],{"type":26,"value":2928},"CanRedo",{"type":21,"tag":1330,"props":2930,"children":2931},{"style":1356},[2932],{"type":26,"value":2747},{"type":21,"tag":1330,"props":2934,"children":2935},{"style":1376},[2936],{"type":26,"value":2752},{"type":21,"tag":1330,"props":2938,"children":2939},{"style":1356},[2940],{"type":26,"value":1699},{"type":21,"tag":1330,"props":2942,"children":2944},{"class":1332,"line":2943},38,[2945,2949,2953,2957,2961,2965,2969],{"type":21,"tag":1330,"props":2946,"children":2947},{"style":1336},[2948],{"type":26,"value":2765},{"type":21,"tag":1330,"props":2950,"children":2951},{"style":1415},[2952],{"type":26,"value":2549},{"type":21,"tag":1330,"props":2954,"children":2955},{"style":1356},[2956],{"type":26,"value":1901},{"type":21,"tag":1330,"props":2958,"children":2959},{"style":1893},[2960],{"type":26,"value":2260},{"type":21,"tag":1330,"props":2962,"children":2963},{"style":1356},[2964],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2966,"children":2967},{"style":1415},[2968],{"type":26,"value":2786},{"type":21,"tag":1330,"props":2970,"children":2971},{"style":1356},[2972],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2974,"children":2976},{"class":1332,"line":2975},39,[2977,2981,2985,2989],{"type":21,"tag":1330,"props":2978,"children":2979},{"style":1893},[2980],{"type":26,"value":2569},{"type":21,"tag":1330,"props":2982,"children":2983},{"style":1356},[2984],{"type":26,"value":2003},{"type":21,"tag":1330,"props":2986,"children":2987},{"style":1415},[2988],{"type":26,"value":2103},{"type":21,"tag":1330,"props":2990,"children":2991},{"style":1356},[2992],{"type":26,"value":1423},{"type":21,"tag":1330,"props":2994,"children":2996},{"class":1332,"line":2995},40,[2997,3001,3005,3009,3013,3017],{"type":21,"tag":1330,"props":2998,"children":2999},{"style":1893},[3000],{"type":26,"value":2590},{"type":21,"tag":1330,"props":3002,"children":3003},{"style":1356},[3004],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3006,"children":3007},{"style":1415},[3008],{"type":26,"value":2599},{"type":21,"tag":1330,"props":3010,"children":3011},{"style":1356},[3012],{"type":26,"value":1837},{"type":21,"tag":1330,"props":3014,"children":3015},{"style":1893},[3016],{"type":26,"value":2608},{"type":21,"tag":1330,"props":3018,"children":3019},{"style":1356},[3020],{"type":26,"value":2029},{"type":21,"tag":1330,"props":3022,"children":3024},{"class":1332,"line":3023},41,[3025,3029,3033,3037,3041],{"type":21,"tag":1330,"props":3026,"children":3027},{"style":1893},[3028],{"type":26,"value":2656},{"type":21,"tag":1330,"props":3030,"children":3031},{"style":1336},[3032],{"type":26,"value":2325},{"type":21,"tag":1330,"props":3034,"children":3035},{"style":1356},[3036],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3038,"children":3039},{"style":1415},[3040],{"type":26,"value":2669},{"type":21,"tag":1330,"props":3042,"children":3043},{"style":1356},[3044],{"type":26,"value":1423},{"type":21,"tag":1330,"props":3046,"children":3048},{"class":1332,"line":3047},42,[3049],{"type":21,"tag":1330,"props":3050,"children":3051},{"style":1356},[3052],{"type":26,"value":1960},{"type":21,"tag":1330,"props":3054,"children":3056},{"class":1332,"line":3055},43,[3057],{"type":21,"tag":1330,"props":3058,"children":3059},{"style":1356},[3060],{"type":26,"value":1575},{"type":21,"tag":22,"props":3062,"children":3063},{},[3064],{"type":26,"value":3065},"ここで注目ポイントが2つあります。",{"type":21,"tag":22,"props":3067,"children":3068},{},[3069,3071,3076,3078,3084],{"type":26,"value":3070},"1つ目は",{"type":21,"tag":566,"props":3072,"children":3074},{"className":3073},[],[3075],{"type":26,"value":2103},{"type":26,"value":3077},"の中で",{"type":21,"tag":566,"props":3079,"children":3081},{"className":3080},[],[3082],{"type":26,"value":3083},"_redoStack.Clear()",{"type":26,"value":3085},"を呼んでいる点です。Undoした後に新しい操作を実行したら、「未来の枝」（Redo可能だった履歴）は全部捨てるのが一般的な挙動です。Adobe系のアプリでUndo→別の操作をするとRedoが効かなくなるのと同じ挙動で、ユーザーが違和感なく使えます。",{"type":21,"tag":22,"props":3087,"children":3088},{},[3089,3091,3097,3099,3105,3107,3112,3114,3119],{"type":26,"value":3090},"2つ目は",{"type":21,"tag":566,"props":3092,"children":3094},{"className":3093},[],[3095],{"type":26,"value":3096},"StateChanged",{"type":26,"value":3098},"イベントです。Blazor側ではこのイベントに購読して、UndoボタンとRedoボタンの",{"type":21,"tag":566,"props":3100,"children":3102},{"className":3101},[],[3103],{"type":26,"value":3104},"disabled",{"type":26,"value":3106},"属性を",{"type":21,"tag":566,"props":3108,"children":3110},{"className":3109},[],[3111],{"type":26,"value":2742},{"type":26,"value":3113}," / ",{"type":21,"tag":566,"props":3115,"children":3117},{"className":3116},[],[3118],{"type":26,"value":2928},{"type":26,"value":3120},"に連動させます。イベント駆動にしておけば、どこからコマンドを実行してもUIが自動で追従します。",{"type":21,"tag":65,"props":3122,"children":3124},{"id":3123},"trimhistory履歴上限でメモリリークを防ぐ",[3125],{"type":26,"value":3126},"TrimHistory：履歴上限でメモリリークを防ぐ",{"type":21,"tag":22,"props":3128,"children":3129},{},[3130],{"type":26,"value":3131},"長時間編集していると履歴はどんどん積み上がります。PICOMの楽譜エディタでは、音符の配置・削除・移動だけでも1時間の編集で数百件を超えるので、履歴を無制限に持っているとメモリが膨れ上がる恐れがあります。",{"type":21,"tag":22,"props":3133,"children":3134},{},[3135,3137,3143],{"type":26,"value":3136},"そこで、最大履歴数を超えたら古いものから捨てる",{"type":21,"tag":566,"props":3138,"children":3140},{"className":3139},[],[3141],{"type":26,"value":3142},"TrimHistory",{"type":26,"value":3144},"を実装しました。",{"type":21,"tag":561,"props":3146,"children":3148},{"className":1322,"code":3147,"language":1324,"meta":8,"style":8},"private void TrimHistory()\n{\n    while (_undoStack.Count > _maxHistorySize)\n    {\n        var list = _undoStack.ToList();\n        list.RemoveAt(list.Count - 1);\n        _undoStack.Clear();\n        foreach (var item in list.AsEnumerable().Reverse())\n        {\n            _undoStack.Push(item);\n        }\n    }\n}\n",[3149],{"type":21,"tag":566,"props":3150,"children":3151},{"__ignoreMap":8},[3152,3173,3180,3216,3223,3256,3303,3322,3377,3385,3414,3422,3429],{"type":21,"tag":1330,"props":3153,"children":3154},{"class":1332,"line":12},[3155,3160,3164,3169],{"type":21,"tag":1330,"props":3156,"children":3157},{"style":1336},[3158],{"type":26,"value":3159},"private",{"type":21,"tag":1330,"props":3161,"children":3162},{"style":1376},[3163],{"type":26,"value":1981},{"type":21,"tag":1330,"props":3165,"children":3166},{"style":1415},[3167],{"type":26,"value":3168}," TrimHistory",{"type":21,"tag":1330,"props":3170,"children":3171},{"style":1356},[3172],{"type":26,"value":2710},{"type":21,"tag":1330,"props":3174,"children":3175},{"class":1332,"line":39},[3176],{"type":21,"tag":1330,"props":3177,"children":3178},{"style":1356},[3179],{"type":26,"value":1359},{"type":21,"tag":1330,"props":3181,"children":3182},{"class":1332,"line":1195},[3183,3188,3192,3196,3200,3204,3208,3212],{"type":21,"tag":1330,"props":3184,"children":3185},{"style":1376},[3186],{"type":26,"value":3187},"    while",{"type":21,"tag":1330,"props":3189,"children":3190},{"style":1356},[3191],{"type":26,"value":2732},{"type":21,"tag":1330,"props":3193,"children":3194},{"style":1893},[3195],{"type":26,"value":2143},{"type":21,"tag":1330,"props":3197,"children":3198},{"style":1356},[3199],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3201,"children":3202},{"style":1893},[3203],{"type":26,"value":2456},{"type":21,"tag":1330,"props":3205,"children":3206},{"style":1356},[3207],{"type":26,"value":2461},{"type":21,"tag":1330,"props":3209,"children":3210},{"style":1893},[3211],{"type":26,"value":2292},{"type":21,"tag":1330,"props":3213,"children":3214},{"style":1356},[3215],{"type":26,"value":1879},{"type":21,"tag":1330,"props":3217,"children":3218},{"class":1332,"line":1406},[3219],{"type":21,"tag":1330,"props":3220,"children":3221},{"style":1356},[3222],{"type":26,"value":1887},{"type":21,"tag":1330,"props":3224,"children":3225},{"class":1332,"line":1426},[3226,3230,3235,3239,3243,3247,3252],{"type":21,"tag":1330,"props":3227,"children":3228},{"style":1336},[3229],{"type":26,"value":2765},{"type":21,"tag":1330,"props":3231,"children":3232},{"style":1415},[3233],{"type":26,"value":3234}," list",{"type":21,"tag":1330,"props":3236,"children":3237},{"style":1356},[3238],{"type":26,"value":1901},{"type":21,"tag":1330,"props":3240,"children":3241},{"style":1893},[3242],{"type":26,"value":2215},{"type":21,"tag":1330,"props":3244,"children":3245},{"style":1356},[3246],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3248,"children":3249},{"style":1415},[3250],{"type":26,"value":3251},"ToList",{"type":21,"tag":1330,"props":3253,"children":3254},{"style":1356},[3255],{"type":26,"value":1423},{"type":21,"tag":1330,"props":3257,"children":3258},{"class":1332,"line":1436},[3259,3264,3268,3272,3276,3281,3285,3289,3294,3299],{"type":21,"tag":1330,"props":3260,"children":3261},{"style":1893},[3262],{"type":26,"value":3263},"        list",{"type":21,"tag":1330,"props":3265,"children":3266},{"style":1356},[3267],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3269,"children":3270},{"style":1415},[3271],{"type":26,"value":2066},{"type":21,"tag":1330,"props":3273,"children":3274},{"style":1356},[3275],{"type":26,"value":1837},{"type":21,"tag":1330,"props":3277,"children":3278},{"style":1893},[3279],{"type":26,"value":3280},"list",{"type":21,"tag":1330,"props":3282,"children":3283},{"style":1356},[3284],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3286,"children":3287},{"style":1893},[3288],{"type":26,"value":2456},{"type":21,"tag":1330,"props":3290,"children":3291},{"style":1336},[3292],{"type":26,"value":3293}," -",{"type":21,"tag":1330,"props":3295,"children":3296},{"style":2374},[3297],{"type":26,"value":3298}," 1",{"type":21,"tag":1330,"props":3300,"children":3301},{"style":1356},[3302],{"type":26,"value":2029},{"type":21,"tag":1330,"props":3304,"children":3305},{"class":1332,"line":1473},[3306,3310,3314,3318],{"type":21,"tag":1330,"props":3307,"children":3308},{"style":1893},[3309],{"type":26,"value":2590},{"type":21,"tag":1330,"props":3311,"children":3312},{"style":1356},[3313],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3315,"children":3316},{"style":1415},[3317],{"type":26,"value":2630},{"type":21,"tag":1330,"props":3319,"children":3320},{"style":1356},[3321],{"type":26,"value":1423},{"type":21,"tag":1330,"props":3323,"children":3324},{"class":1332,"line":1490},[3325,3330,3334,3339,3344,3349,3353,3357,3362,3367,3372],{"type":21,"tag":1330,"props":3326,"children":3327},{"style":1376},[3328],{"type":26,"value":3329},"        foreach",{"type":21,"tag":1330,"props":3331,"children":3332},{"style":1356},[3333],{"type":26,"value":2732},{"type":21,"tag":1330,"props":3335,"children":3336},{"style":1336},[3337],{"type":26,"value":3338},"var",{"type":21,"tag":1330,"props":3340,"children":3341},{"style":1415},[3342],{"type":26,"value":3343}," item",{"type":21,"tag":1330,"props":3345,"children":3346},{"style":1376},[3347],{"type":26,"value":3348}," in",{"type":21,"tag":1330,"props":3350,"children":3351},{"style":1893},[3352],{"type":26,"value":3234},{"type":21,"tag":1330,"props":3354,"children":3355},{"style":1356},[3356],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3358,"children":3359},{"style":1415},[3360],{"type":26,"value":3361},"AsEnumerable",{"type":21,"tag":1330,"props":3363,"children":3364},{"style":1356},[3365],{"type":26,"value":3366},"().",{"type":21,"tag":1330,"props":3368,"children":3369},{"style":1415},[3370],{"type":26,"value":3371},"Reverse",{"type":21,"tag":1330,"props":3373,"children":3374},{"style":1356},[3375],{"type":26,"value":3376},"())\n",{"type":21,"tag":1330,"props":3378,"children":3379},{"class":1332,"line":1498},[3380],{"type":21,"tag":1330,"props":3381,"children":3382},{"style":1356},[3383],{"type":26,"value":3384},"        {\n",{"type":21,"tag":1330,"props":3386,"children":3387},{"class":1332,"line":1535},[3388,3393,3397,3401,3405,3410],{"type":21,"tag":1330,"props":3389,"children":3390},{"style":1893},[3391],{"type":26,"value":3392},"            _undoStack",{"type":21,"tag":1330,"props":3394,"children":3395},{"style":1356},[3396],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3398,"children":3399},{"style":1415},[3400],{"type":26,"value":2599},{"type":21,"tag":1330,"props":3402,"children":3403},{"style":1356},[3404],{"type":26,"value":1837},{"type":21,"tag":1330,"props":3406,"children":3407},{"style":1893},[3408],{"type":26,"value":3409},"item",{"type":21,"tag":1330,"props":3411,"children":3412},{"style":1356},[3413],{"type":26,"value":2029},{"type":21,"tag":1330,"props":3415,"children":3416},{"class":1332,"line":1569},[3417],{"type":21,"tag":1330,"props":3418,"children":3419},{"style":1356},[3420],{"type":26,"value":3421},"        }\n",{"type":21,"tag":1330,"props":3423,"children":3424},{"class":1332,"line":1912},[3425],{"type":21,"tag":1330,"props":3426,"children":3427},{"style":1356},[3428],{"type":26,"value":1960},{"type":21,"tag":1330,"props":3430,"children":3431},{"class":1332,"line":1933},[3432],{"type":21,"tag":1330,"props":3433,"children":3434},{"style":1356},[3435],{"type":26,"value":1575},{"type":21,"tag":22,"props":3437,"children":3438},{},[3439,3445,3447,3453,3455,3461],{"type":21,"tag":566,"props":3440,"children":3442},{"className":3441},[],[3443],{"type":26,"value":3444},"Stack\u003CT>",{"type":26,"value":3446},"は最古要素を直接削除するAPIを持っていないので、一度",{"type":21,"tag":566,"props":3448,"children":3450},{"className":3449},[],[3451],{"type":26,"value":3452},"List",{"type":26,"value":3454},"に展開して最後（最古）を削り、再度Pushし直しています。愚直ですが確実で、",{"type":21,"tag":566,"props":3456,"children":3458},{"className":3457},[],[3459],{"type":26,"value":3460},"_maxHistorySize = 100",{"type":26,"value":3462},"程度なら実行コストは無視できる範囲です。",{"type":21,"tag":383,"props":3464,"children":3467},{"cons-label":3465,"pros-label":3466},"デメリット","メリット",[3468,3508],{"type":21,"tag":389,"props":3469,"children":3470},{"v-slot:pros":8},[3471],{"type":21,"tag":90,"props":3472,"children":3473},{},[3474,3487,3503],{"type":21,"tag":94,"props":3475,"children":3476},{},[3477,3479,3485],{"type":26,"value":3478},"Undo/Redoの挙動が",{"type":21,"tag":566,"props":3480,"children":3482},{"className":3481},[],[3483],{"type":26,"value":3484},"Push/Pop",{"type":26,"value":3486},"と綺麗に一致して読みやすい",{"type":21,"tag":94,"props":3488,"children":3489},{},[3490,3495,3496,3501],{"type":21,"tag":566,"props":3491,"children":3493},{"className":3492},[],[3494],{"type":26,"value":2630},{"type":26,"value":3113},{"type":21,"tag":566,"props":3497,"children":3499},{"className":3498},[],[3500],{"type":26,"value":2456},{"type":26,"value":3502},"などの基本APIで十分足りる",{"type":21,"tag":94,"props":3504,"children":3505},{},[3506],{"type":26,"value":3507},"履歴構造の意図が型から自明",{"type":21,"tag":389,"props":3509,"children":3510},{"v-slot:cons":8},[3511,3524],{"type":21,"tag":94,"props":3512,"children":3513},{},[3514,3516,3522],{"type":26,"value":3515},"最古要素の削除に展開→詰め直しが必要（",{"type":21,"tag":566,"props":3517,"children":3519},{"className":3518},[],[3520],{"type":26,"value":3521},"LinkedList",{"type":26,"value":3523},"ならO(1)）",{"type":21,"tag":94,"props":3525,"children":3526},{},[3527],{"type":26,"value":3528},"双方向からの参照が欲しくなった時に不便",{"type":21,"tag":22,"props":3530,"children":3531},{},[3532,3534,3540,3542,3548],{"type":26,"value":3533},"もし履歴数が数万を想定する場合は",{"type":21,"tag":566,"props":3535,"children":3537},{"className":3536},[],[3538],{"type":26,"value":3539},"LinkedList\u003CICommand>",{"type":26,"value":3541},"や",{"type":21,"tag":566,"props":3543,"children":3545},{"className":3544},[],[3546],{"type":26,"value":3547},"Deque",{"type":26,"value":3549},"的な構造を使った方が良いのですが、エディタのUndo履歴は100件前後で十分なので、可読性を優先してStackにしています。",{"type":21,"tag":65,"props":3551,"children":3553},{"id":3552},"compositecommand複数操作を1手にまとめる",[3554],{"type":26,"value":3555},"CompositeCommand：複数操作を1手にまとめる",{"type":21,"tag":22,"props":3557,"children":3558},{},[3559,3561,3566,3568,3573,3575,3581],{"type":26,"value":3560},"実装していて一番「これがないとまずい」と感じたのが",{"type":21,"tag":566,"props":3562,"children":3564},{"className":3563},[],[3565],{"type":26,"value":1283},{"type":26,"value":3567},"です。たとえばピアノロール上で",{"type":21,"tag":140,"props":3569,"children":3570},{},[3571],{"type":26,"value":3572},"複数の音符を選択して一括で下に動かす",{"type":26,"value":3574},"操作を考えると、これを1つ1つ",{"type":21,"tag":566,"props":3576,"children":3578},{"className":3577},[],[3579],{"type":26,"value":3580},"MoveNotesCommand",{"type":26,"value":3582},"として履歴に積んだら、Undoを何回も押さないと元の状態に戻りません。",{"type":21,"tag":22,"props":3584,"children":3585},{},[3586],{"type":26,"value":3587},"解決策は「中に複数のコマンドを持ち、Execute/Undoで全部を順番に回すコマンド」を作ることです。",{"type":21,"tag":561,"props":3589,"children":3591},{"className":1322,"code":3590,"language":1324,"meta":8,"style":8},"public class CompositeCommand : ICommand\n{\n    private readonly List\u003CICommand> _commands;\n    private readonly string _description;\n\n    public string Description => _description;\n\n    public CompositeCommand(string description, IEnumerable\u003CICommand> commands)\n    {\n        _description = description;\n        _commands = commands.ToList();\n    }\n\n    public void Execute()\n    {\n        foreach (var command in _commands)\n        {\n            command.Execute();\n        }\n    }\n\n    public void Undo()\n    {\n        for (int i = _commands.Count - 1; i >= 0; i--)\n        {\n            _commands[i].Undo();\n        }\n    }\n}\n",[3592],{"type":21,"tag":566,"props":3593,"children":3594},{"__ignoreMap":8},[3595,3619,3626,3663,3687,3694,3721,3728,3783,3790,3810,3838,3845,3852,3871,3878,3909,3916,3936,3943,3950,3957,3976,3983,4062,4069,4100,4107,4114],{"type":21,"tag":1330,"props":3596,"children":3597},{"class":1332,"line":12},[3598,3602,3606,3611,3615],{"type":21,"tag":1330,"props":3599,"children":3600},{"style":1336},[3601],{"type":26,"value":1339},{"type":21,"tag":1330,"props":3603,"children":3604},{"style":1336},[3605],{"type":26,"value":1650},{"type":21,"tag":1330,"props":3607,"children":3608},{"style":1347},[3609],{"type":26,"value":3610}," CompositeCommand",{"type":21,"tag":1330,"props":3612,"children":3613},{"style":1356},[3614],{"type":26,"value":1660},{"type":21,"tag":1330,"props":3616,"children":3617},{"style":1347},[3618],{"type":26,"value":1350},{"type":21,"tag":1330,"props":3620,"children":3621},{"class":1332,"line":39},[3622],{"type":21,"tag":1330,"props":3623,"children":3624},{"style":1356},[3625],{"type":26,"value":1359},{"type":21,"tag":1330,"props":3627,"children":3628},{"class":1332,"line":1195},[3629,3633,3637,3642,3646,3650,3654,3659],{"type":21,"tag":1330,"props":3630,"children":3631},{"style":1336},[3632],{"type":26,"value":1679},{"type":21,"tag":1330,"props":3634,"children":3635},{"style":1336},[3636],{"type":26,"value":1684},{"type":21,"tag":1330,"props":3638,"children":3639},{"style":1347},[3640],{"type":26,"value":3641}," List",{"type":21,"tag":1330,"props":3643,"children":3644},{"style":1356},[3645],{"type":26,"value":1373},{"type":21,"tag":1330,"props":3647,"children":3648},{"style":1347},[3649],{"type":26,"value":1267},{"type":21,"tag":1330,"props":3651,"children":3652},{"style":1356},[3653],{"type":26,"value":1384},{"type":21,"tag":1330,"props":3655,"children":3656},{"style":1415},[3657],{"type":26,"value":3658}," _commands",{"type":21,"tag":1330,"props":3660,"children":3661},{"style":1356},[3662],{"type":26,"value":1699},{"type":21,"tag":1330,"props":3664,"children":3665},{"class":1332,"line":1406},[3666,3670,3674,3678,3683],{"type":21,"tag":1330,"props":3667,"children":3668},{"style":1336},[3669],{"type":26,"value":1679},{"type":21,"tag":1330,"props":3671,"children":3672},{"style":1336},[3673],{"type":26,"value":1684},{"type":21,"tag":1330,"props":3675,"children":3676},{"style":1376},[3677],{"type":26,"value":1769},{"type":21,"tag":1330,"props":3679,"children":3680},{"style":1415},[3681],{"type":26,"value":3682}," _description",{"type":21,"tag":1330,"props":3684,"children":3685},{"style":1356},[3686],{"type":26,"value":1699},{"type":21,"tag":1330,"props":3688,"children":3689},{"class":1332,"line":1426},[3690],{"type":21,"tag":1330,"props":3691,"children":3692},{"emptyLinePlaceholder":1430},[3693],{"type":26,"value":1433},{"type":21,"tag":1330,"props":3695,"children":3696},{"class":1332,"line":1436},[3697,3701,3705,3709,3713,3717],{"type":21,"tag":1330,"props":3698,"children":3699},{"style":1336},[3700],{"type":26,"value":1764},{"type":21,"tag":1330,"props":3702,"children":3703},{"style":1376},[3704],{"type":26,"value":1769},{"type":21,"tag":1330,"props":3706,"children":3707},{"style":1415},[3708],{"type":26,"value":1546},{"type":21,"tag":1330,"props":3710,"children":3711},{"style":1336},[3712],{"type":26,"value":1778},{"type":21,"tag":1330,"props":3714,"children":3715},{"style":1893},[3716],{"type":26,"value":3682},{"type":21,"tag":1330,"props":3718,"children":3719},{"style":1356},[3720],{"type":26,"value":1699},{"type":21,"tag":1330,"props":3722,"children":3723},{"class":1332,"line":1473},[3724],{"type":21,"tag":1330,"props":3725,"children":3726},{"emptyLinePlaceholder":1430},[3727],{"type":26,"value":1433},{"type":21,"tag":1330,"props":3729,"children":3730},{"class":1332,"line":1490},[3731,3735,3739,3743,3748,3753,3757,3762,3766,3770,3774,3779],{"type":21,"tag":1330,"props":3732,"children":3733},{"style":1336},[3734],{"type":26,"value":1764},{"type":21,"tag":1330,"props":3736,"children":3737},{"style":1415},[3738],{"type":26,"value":3610},{"type":21,"tag":1330,"props":3740,"children":3741},{"style":1356},[3742],{"type":26,"value":1837},{"type":21,"tag":1330,"props":3744,"children":3745},{"style":1376},[3746],{"type":26,"value":3747},"string",{"type":21,"tag":1330,"props":3749,"children":3750},{"style":1415},[3751],{"type":26,"value":3752}," description",{"type":21,"tag":1330,"props":3754,"children":3755},{"style":1356},[3756],{"type":26,"value":1852},{"type":21,"tag":1330,"props":3758,"children":3759},{"style":1347},[3760],{"type":26,"value":3761}," IEnumerable",{"type":21,"tag":1330,"props":3763,"children":3764},{"style":1356},[3765],{"type":26,"value":1373},{"type":21,"tag":1330,"props":3767,"children":3768},{"style":1347},[3769],{"type":26,"value":1267},{"type":21,"tag":1330,"props":3771,"children":3772},{"style":1356},[3773],{"type":26,"value":1384},{"type":21,"tag":1330,"props":3775,"children":3776},{"style":1415},[3777],{"type":26,"value":3778}," commands",{"type":21,"tag":1330,"props":3780,"children":3781},{"style":1356},[3782],{"type":26,"value":1879},{"type":21,"tag":1330,"props":3784,"children":3785},{"class":1332,"line":1498},[3786],{"type":21,"tag":1330,"props":3787,"children":3788},{"style":1356},[3789],{"type":26,"value":1887},{"type":21,"tag":1330,"props":3791,"children":3792},{"class":1332,"line":1535},[3793,3798,3802,3806],{"type":21,"tag":1330,"props":3794,"children":3795},{"style":1893},[3796],{"type":26,"value":3797},"        _description",{"type":21,"tag":1330,"props":3799,"children":3800},{"style":1356},[3801],{"type":26,"value":1901},{"type":21,"tag":1330,"props":3803,"children":3804},{"style":1893},[3805],{"type":26,"value":3752},{"type":21,"tag":1330,"props":3807,"children":3808},{"style":1356},[3809],{"type":26,"value":1699},{"type":21,"tag":1330,"props":3811,"children":3812},{"class":1332,"line":1569},[3813,3818,3822,3826,3830,3834],{"type":21,"tag":1330,"props":3814,"children":3815},{"style":1893},[3816],{"type":26,"value":3817},"        _commands",{"type":21,"tag":1330,"props":3819,"children":3820},{"style":1356},[3821],{"type":26,"value":1901},{"type":21,"tag":1330,"props":3823,"children":3824},{"style":1893},[3825],{"type":26,"value":3778},{"type":21,"tag":1330,"props":3827,"children":3828},{"style":1356},[3829],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3831,"children":3832},{"style":1415},[3833],{"type":26,"value":3251},{"type":21,"tag":1330,"props":3835,"children":3836},{"style":1356},[3837],{"type":26,"value":1423},{"type":21,"tag":1330,"props":3839,"children":3840},{"class":1332,"line":1912},[3841],{"type":21,"tag":1330,"props":3842,"children":3843},{"style":1356},[3844],{"type":26,"value":1960},{"type":21,"tag":1330,"props":3846,"children":3847},{"class":1332,"line":1933},[3848],{"type":21,"tag":1330,"props":3849,"children":3850},{"emptyLinePlaceholder":1430},[3851],{"type":26,"value":1433},{"type":21,"tag":1330,"props":3853,"children":3854},{"class":1332,"line":1954},[3855,3859,3863,3867],{"type":21,"tag":1330,"props":3856,"children":3857},{"style":1336},[3858],{"type":26,"value":1764},{"type":21,"tag":1330,"props":3860,"children":3861},{"style":1376},[3862],{"type":26,"value":1981},{"type":21,"tag":1330,"props":3864,"children":3865},{"style":1415},[3866],{"type":26,"value":1418},{"type":21,"tag":1330,"props":3868,"children":3869},{"style":1356},[3870],{"type":26,"value":2710},{"type":21,"tag":1330,"props":3872,"children":3873},{"class":1332,"line":1963},[3874],{"type":21,"tag":1330,"props":3875,"children":3876},{"style":1356},[3877],{"type":26,"value":1887},{"type":21,"tag":1330,"props":3879,"children":3880},{"class":1332,"line":1971},[3881,3885,3889,3893,3897,3901,3905],{"type":21,"tag":1330,"props":3882,"children":3883},{"style":1376},[3884],{"type":26,"value":3329},{"type":21,"tag":1330,"props":3886,"children":3887},{"style":1356},[3888],{"type":26,"value":2732},{"type":21,"tag":1330,"props":3890,"children":3891},{"style":1336},[3892],{"type":26,"value":3338},{"type":21,"tag":1330,"props":3894,"children":3895},{"style":1415},[3896],{"type":26,"value":2549},{"type":21,"tag":1330,"props":3898,"children":3899},{"style":1376},[3900],{"type":26,"value":3348},{"type":21,"tag":1330,"props":3902,"children":3903},{"style":1893},[3904],{"type":26,"value":3658},{"type":21,"tag":1330,"props":3906,"children":3907},{"style":1356},[3908],{"type":26,"value":1879},{"type":21,"tag":1330,"props":3910,"children":3911},{"class":1332,"line":2032},[3912],{"type":21,"tag":1330,"props":3913,"children":3914},{"style":1356},[3915],{"type":26,"value":3384},{"type":21,"tag":1330,"props":3917,"children":3918},{"class":1332,"line":2089},[3919,3924,3928,3932],{"type":21,"tag":1330,"props":3920,"children":3921},{"style":1893},[3922],{"type":26,"value":3923},"            command",{"type":21,"tag":1330,"props":3925,"children":3926},{"style":1356},[3927],{"type":26,"value":2003},{"type":21,"tag":1330,"props":3929,"children":3930},{"style":1415},[3931],{"type":26,"value":2103},{"type":21,"tag":1330,"props":3933,"children":3934},{"style":1356},[3935],{"type":26,"value":1423},{"type":21,"tag":1330,"props":3937,"children":3938},{"class":1332,"line":2563},[3939],{"type":21,"tag":1330,"props":3940,"children":3941},{"style":1356},[3942],{"type":26,"value":3421},{"type":21,"tag":1330,"props":3944,"children":3945},{"class":1332,"line":2584},[3946],{"type":21,"tag":1330,"props":3947,"children":3948},{"style":1356},[3949],{"type":26,"value":1960},{"type":21,"tag":1330,"props":3951,"children":3952},{"class":1332,"line":2615},[3953],{"type":21,"tag":1330,"props":3954,"children":3955},{"emptyLinePlaceholder":1430},[3956],{"type":26,"value":1433},{"type":21,"tag":1330,"props":3958,"children":3959},{"class":1332,"line":2637},[3960,3964,3968,3972],{"type":21,"tag":1330,"props":3961,"children":3962},{"style":1336},[3963],{"type":26,"value":1764},{"type":21,"tag":1330,"props":3965,"children":3966},{"style":1376},[3967],{"type":26,"value":1981},{"type":21,"tag":1330,"props":3969,"children":3970},{"style":1415},[3971],{"type":26,"value":1483},{"type":21,"tag":1330,"props":3973,"children":3974},{"style":1356},[3975],{"type":26,"value":2710},{"type":21,"tag":1330,"props":3977,"children":3978},{"class":1332,"line":2650},[3979],{"type":21,"tag":1330,"props":3980,"children":3981},{"style":1356},[3982],{"type":26,"value":1887},{"type":21,"tag":1330,"props":3984,"children":3985},{"class":1332,"line":2676},[3986,3991,3995,3999,4004,4008,4012,4016,4020,4024,4028,4032,4036,4041,4045,4049,4053,4058],{"type":21,"tag":1330,"props":3987,"children":3988},{"style":1376},[3989],{"type":26,"value":3990},"        for",{"type":21,"tag":1330,"props":3992,"children":3993},{"style":1356},[3994],{"type":26,"value":2732},{"type":21,"tag":1330,"props":3996,"children":3997},{"style":1376},[3998],{"type":26,"value":2362},{"type":21,"tag":1330,"props":4000,"children":4001},{"style":1415},[4002],{"type":26,"value":4003}," i",{"type":21,"tag":1330,"props":4005,"children":4006},{"style":1356},[4007],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4009,"children":4010},{"style":1893},[4011],{"type":26,"value":3658},{"type":21,"tag":1330,"props":4013,"children":4014},{"style":1356},[4015],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4017,"children":4018},{"style":1893},[4019],{"type":26,"value":2456},{"type":21,"tag":1330,"props":4021,"children":4022},{"style":1336},[4023],{"type":26,"value":3293},{"type":21,"tag":1330,"props":4025,"children":4026},{"style":2374},[4027],{"type":26,"value":3298},{"type":21,"tag":1330,"props":4029,"children":4030},{"style":1356},[4031],{"type":26,"value":1561},{"type":21,"tag":1330,"props":4033,"children":4034},{"style":1893},[4035],{"type":26,"value":4003},{"type":21,"tag":1330,"props":4037,"children":4038},{"style":1356},[4039],{"type":26,"value":4040}," >=",{"type":21,"tag":1330,"props":4042,"children":4043},{"style":2374},[4044],{"type":26,"value":2466},{"type":21,"tag":1330,"props":4046,"children":4047},{"style":1356},[4048],{"type":26,"value":1561},{"type":21,"tag":1330,"props":4050,"children":4051},{"style":1893},[4052],{"type":26,"value":4003},{"type":21,"tag":1330,"props":4054,"children":4055},{"style":1336},[4056],{"type":26,"value":4057},"--",{"type":21,"tag":1330,"props":4059,"children":4060},{"style":1356},[4061],{"type":26,"value":1879},{"type":21,"tag":1330,"props":4063,"children":4064},{"class":1332,"line":2684},[4065],{"type":21,"tag":1330,"props":4066,"children":4067},{"style":1356},[4068],{"type":26,"value":3384},{"type":21,"tag":1330,"props":4070,"children":4071},{"class":1332,"line":2692},[4072,4077,4082,4087,4092,4096],{"type":21,"tag":1330,"props":4073,"children":4074},{"style":1893},[4075],{"type":26,"value":4076},"            _commands",{"type":21,"tag":1330,"props":4078,"children":4079},{"style":1356},[4080],{"type":26,"value":4081},"[",{"type":21,"tag":1330,"props":4083,"children":4084},{"style":1893},[4085],{"type":26,"value":4086},"i",{"type":21,"tag":1330,"props":4088,"children":4089},{"style":1356},[4090],{"type":26,"value":4091},"].",{"type":21,"tag":1330,"props":4093,"children":4094},{"style":1415},[4095],{"type":26,"value":2110},{"type":21,"tag":1330,"props":4097,"children":4098},{"style":1356},[4099],{"type":26,"value":1423},{"type":21,"tag":1330,"props":4101,"children":4102},{"class":1332,"line":2713},[4103],{"type":21,"tag":1330,"props":4104,"children":4105},{"style":1356},[4106],{"type":26,"value":3421},{"type":21,"tag":1330,"props":4108,"children":4109},{"class":1332,"line":2721},[4110],{"type":21,"tag":1330,"props":4111,"children":4112},{"style":1356},[4113],{"type":26,"value":1960},{"type":21,"tag":1330,"props":4115,"children":4116},{"class":1332,"line":2759},[4117],{"type":21,"tag":1330,"props":4118,"children":4119},{"style":1356},[4120],{"type":26,"value":1575},{"type":21,"tag":22,"props":4122,"children":4123},{},[4124,4126,4131],{"type":26,"value":4125},"地味に大事なのが",{"type":21,"tag":140,"props":4127,"children":4128},{},[4129],{"type":26,"value":4130},"Undo時は逆順で回す",{"type":26,"value":4132},"ところです。「AをしてからBをした」なら、戻す時は「Bを戻してからAを戻す」のが正しい順序です。コマンド間に副作用の依存があるとこの順序を間違えた時にバグるので、注意ポイントです。",{"type":21,"tag":22,"props":4134,"children":4135},{},[4136],{"type":26,"value":4137},"使い方はこんな感じです。",{"type":21,"tag":561,"props":4139,"children":4141},{"className":1322,"code":4140,"language":1324,"meta":8,"style":8},"var commands = selectedNotes.Select(note => new MoveNoteCommand(track, note, delta));\nvar composite = new CompositeCommand($\"{selectedNotes.Count}個の音符を移動\", commands);\n_undoRedoManager.Execute(composite);\n",[4142],{"type":21,"tag":566,"props":4143,"children":4144},{"__ignoreMap":8},[4145,4228,4303],{"type":21,"tag":1330,"props":4146,"children":4147},{"class":1332,"line":12},[4148,4152,4156,4160,4165,4169,4174,4178,4183,4187,4191,4196,4200,4205,4209,4214,4218,4223],{"type":21,"tag":1330,"props":4149,"children":4150},{"style":1336},[4151],{"type":26,"value":3338},{"type":21,"tag":1330,"props":4153,"children":4154},{"style":1415},[4155],{"type":26,"value":3778},{"type":21,"tag":1330,"props":4157,"children":4158},{"style":1356},[4159],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4161,"children":4162},{"style":1893},[4163],{"type":26,"value":4164}," selectedNotes",{"type":21,"tag":1330,"props":4166,"children":4167},{"style":1356},[4168],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4170,"children":4171},{"style":1415},[4172],{"type":26,"value":4173},"Select",{"type":21,"tag":1330,"props":4175,"children":4176},{"style":1356},[4177],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4179,"children":4180},{"style":1415},[4181],{"type":26,"value":4182},"note",{"type":21,"tag":1330,"props":4184,"children":4185},{"style":1336},[4186],{"type":26,"value":1778},{"type":21,"tag":1330,"props":4188,"children":4189},{"style":1336},[4190],{"type":26,"value":2224},{"type":21,"tag":1330,"props":4192,"children":4193},{"style":1347},[4194],{"type":26,"value":4195}," MoveNoteCommand",{"type":21,"tag":1330,"props":4197,"children":4198},{"style":1356},[4199],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4201,"children":4202},{"style":1893},[4203],{"type":26,"value":4204},"track",{"type":21,"tag":1330,"props":4206,"children":4207},{"style":1356},[4208],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4210,"children":4211},{"style":1893},[4212],{"type":26,"value":4213}," note",{"type":21,"tag":1330,"props":4215,"children":4216},{"style":1356},[4217],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4219,"children":4220},{"style":1893},[4221],{"type":26,"value":4222}," delta",{"type":21,"tag":1330,"props":4224,"children":4225},{"style":1356},[4226],{"type":26,"value":4227},"));\n",{"type":21,"tag":1330,"props":4229,"children":4230},{"class":1332,"line":39},[4231,4235,4240,4244,4248,4252,4256,4261,4265,4270,4274,4278,4282,4287,4291,4295,4299],{"type":21,"tag":1330,"props":4232,"children":4233},{"style":1336},[4234],{"type":26,"value":3338},{"type":21,"tag":1330,"props":4236,"children":4237},{"style":1415},[4238],{"type":26,"value":4239}," composite",{"type":21,"tag":1330,"props":4241,"children":4242},{"style":1356},[4243],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4245,"children":4246},{"style":1336},[4247],{"type":26,"value":2224},{"type":21,"tag":1330,"props":4249,"children":4250},{"style":1347},[4251],{"type":26,"value":3610},{"type":21,"tag":1330,"props":4253,"children":4254},{"style":1356},[4255],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4257,"children":4258},{"style":1781},[4259],{"type":26,"value":4260},"$\"",{"type":21,"tag":1330,"props":4262,"children":4263},{"style":1356},[4264],{"type":26,"value":1795},{"type":21,"tag":1330,"props":4266,"children":4267},{"style":1787},[4268],{"type":26,"value":4269},"selectedNotes",{"type":21,"tag":1330,"props":4271,"children":4272},{"style":1356},[4273],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4275,"children":4276},{"style":1787},[4277],{"type":26,"value":2456},{"type":21,"tag":1330,"props":4279,"children":4280},{"style":1356},[4281],{"type":26,"value":1805},{"type":21,"tag":1330,"props":4283,"children":4284},{"style":1787},[4285],{"type":26,"value":4286},"個の音符を移動",{"type":21,"tag":1330,"props":4288,"children":4289},{"style":1781},[4290],{"type":26,"value":1810},{"type":21,"tag":1330,"props":4292,"children":4293},{"style":1356},[4294],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4296,"children":4297},{"style":1893},[4298],{"type":26,"value":3778},{"type":21,"tag":1330,"props":4300,"children":4301},{"style":1356},[4302],{"type":26,"value":2029},{"type":21,"tag":1330,"props":4304,"children":4305},{"class":1332,"line":1195},[4306,4311,4315,4319,4323,4328],{"type":21,"tag":1330,"props":4307,"children":4308},{"style":1893},[4309],{"type":26,"value":4310},"_undoRedoManager",{"type":21,"tag":1330,"props":4312,"children":4313},{"style":1356},[4314],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4316,"children":4317},{"style":1415},[4318],{"type":26,"value":2103},{"type":21,"tag":1330,"props":4320,"children":4321},{"style":1356},[4322],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4324,"children":4325},{"style":1893},[4326],{"type":26,"value":4327},"composite",{"type":21,"tag":1330,"props":4329,"children":4330},{"style":1356},[4331],{"type":26,"value":2029},{"type":21,"tag":22,"props":4333,"children":4334},{},[4335],{"type":26,"value":4336},"ユーザー視点では「選択した音符を全部動かす」を1手としてUndoできるので、快適に使えます。",{"type":21,"tag":65,"props":4338,"children":4340},{"id":4339},"まとめ",[4341],{"type":26,"value":4339},{"type":21,"tag":90,"props":4343,"children":4344},{},[4345,4374,4384,4389],{"type":21,"tag":94,"props":4346,"children":4347},{},[4348,4353,4355,4360,4361,4366,4367,4372],{"type":21,"tag":566,"props":4349,"children":4351},{"className":4350},[],[4352],{"type":26,"value":1267},{"type":26,"value":4354},"は",{"type":21,"tag":566,"props":4356,"children":4358},{"className":4357},[],[4359],{"type":26,"value":2103},{"type":26,"value":3113},{"type":21,"tag":566,"props":4362,"children":4364},{"className":4363},[],[4365],{"type":26,"value":2110},{"type":26,"value":3113},{"type":21,"tag":566,"props":4368,"children":4370},{"className":4369},[],[4371],{"type":26,"value":1586},{"type":26,"value":4373},"の3点セットで十分",{"type":21,"tag":94,"props":4375,"children":4376},{},[4377,4382],{"type":21,"tag":566,"props":4378,"children":4380},{"className":4379},[],[4381],{"type":26,"value":1275},{"type":26,"value":4383},"は2本のStackで管理、Redoスタックは新規Executeで破棄",{"type":21,"tag":94,"props":4385,"children":4386},{},[4387],{"type":26,"value":4388},"履歴上限を入れないとメモリ使用量が膨らむことに注意",{"type":21,"tag":94,"props":4390,"children":4391},{},[4392,4394,4399],{"type":26,"value":4393},"複数操作を一括でする",{"type":21,"tag":566,"props":4395,"children":4397},{"className":4396},[],[4398],{"type":26,"value":1283},{"type":26,"value":4400},"は必須レベルで便利。Undo時は逆順に回す",{"type":21,"tag":4402,"props":4403,"children":4404},"style",{},[4405],{"type":26,"value":4406},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":8,"searchDepth":39,"depth":39,"links":4408},[4409,4410,4411,4412,4413,4414,4415],{"id":67,"depth":39,"text":67},{"id":1288,"depth":39,"text":1291},{"id":1311,"depth":39,"text":1314},{"id":2122,"depth":39,"text":2125},{"id":3123,"depth":39,"text":3126},{"id":3552,"depth":39,"text":3555},{"id":4339,"depth":39,"text":4339},"content:articles:tech:blazor:undo-redo-command-pattern.md","articles/tech/blazor/undo-redo-command-pattern.md","articles/tech/blazor/undo-redo-command-pattern",{"_path":4420,"_dir":4421,"_draft":7,"_partial":7,"_locale":8,"title":4422,"description":4423,"date":4424,"tags":4425,"rowTypeId":12,"sitemap":4432,"body":4433,"_type":41,"_id":6618,"_source":43,"_file":6619,"_stem":6620,"_extension":46},"/articles/tech/development/yominchu-vertical-canvas","development","【解説】Canvas で縦書き日本語組版を実装する！詠み人の描画方法","和歌・短歌・俳句を画像化するツール「詠み人」で、CSS writing-mode を使わず Canvas で縦書きを実装した理由と、句読点や括弧などの特殊文字の扱いをまとめます。","2026-04-16",[4426,4427,4428,4429,4430,4431],"Canvas","日本語組版","縦書き","TypeScript","フォント","詠み人",{"loc":4420,"lastmod":4424,"priority":12},{"type":18,"children":4434,"toc":6604},[4435,4439,4459,4479,4495,4500,4571,4584,4589,4601,4606,4611,5194,5214,5234,5240,5260,5273,6065,6070,6090,6096,6123,6135,6140,6152,6157,6334,6360,6365,6384,6397,6528,6549,6553,6595,6600],{"type":21,"tag":65,"props":4436,"children":4437},{"id":67},[4438],{"type":26,"value":67},{"type":21,"tag":22,"props":4440,"children":4441},{},[4442,4444,4450,4452,4457],{"type":26,"value":4443},"和歌・短歌・俳句を縦書きの美しい画像にして SNS に投稿できるツール ",{"type":21,"tag":4445,"props":4446,"children":4448},"a",{"href":4447},"/tools/yominchu",[4449],{"type":26,"value":4431},{"type":26,"value":4451}," を作ったとき、最大の悩みどころは「",{"type":21,"tag":140,"props":4453,"children":4454},{},[4455],{"type":26,"value":4456},"縦書きレイアウトをどう描画するか",{"type":26,"value":4458},"」でした。",{"type":21,"tag":22,"props":4460,"children":4461},{},[4462,4464,4470,4472,4477],{"type":26,"value":4463},"ブラウザには ",{"type":21,"tag":566,"props":4465,"children":4467},{"className":4466},[],[4468],{"type":26,"value":4469},"writing-mode: vertical-rl",{"type":26,"value":4471}," という CSS プロパティがあり、HTML 上で縦書きを表現することはできます。けれども詠み人では最終的に ",{"type":21,"tag":140,"props":4473,"children":4474},{},[4475],{"type":26,"value":4476},"Canvas で1文字ずつ描画する",{"type":26,"value":4478}," 方針にしました。この記事では、その判断と、ハマった特殊文字の扱いをまとめます。",{"type":21,"tag":76,"props":4480,"children":4481},{},[4482],{"type":21,"tag":22,"props":4483,"children":4484},{},[4485,4487,4493],{"type":26,"value":4486},"CSS ",{"type":21,"tag":566,"props":4488,"children":4490},{"className":4489},[],[4491],{"type":26,"value":4492},"writing-mode",{"type":26,"value":4494}," ではなく Canvas 描画を選んだのは、「最終成果物が画像である」という用途の特殊性からでした。句読点の位置調整、長音・括弧の90度回転、フォント読み込み待機など、日本語組版固有の細かいルールを自前でコントロールする必要があり、結果的に Canvas のほうが制御しやすかったです。",{"type":21,"tag":65,"props":4496,"children":4498},{"id":4497},"環境",[4499],{"type":26,"value":4497},{"type":21,"tag":862,"props":4501,"children":4502},{},[4503,4518],{"type":21,"tag":866,"props":4504,"children":4505},{},[4506],{"type":21,"tag":870,"props":4507,"children":4508},{},[4509,4513],{"type":21,"tag":874,"props":4510,"children":4511},{},[4512],{"type":26,"value":878},{"type":21,"tag":874,"props":4514,"children":4515},{},[4516],{"type":26,"value":4517},"技術",{"type":21,"tag":885,"props":4519,"children":4520},{},[4521,4534,4546,4559],{"type":21,"tag":870,"props":4522,"children":4523},{},[4524,4529],{"type":21,"tag":892,"props":4525,"children":4526},{},[4527],{"type":26,"value":4528},"フレームワーク",{"type":21,"tag":892,"props":4530,"children":4531},{},[4532],{"type":26,"value":4533},"Nuxt 4 + Vue 3",{"type":21,"tag":870,"props":4535,"children":4536},{},[4537,4542],{"type":21,"tag":892,"props":4538,"children":4539},{},[4540],{"type":26,"value":4541},"言語",{"type":21,"tag":892,"props":4543,"children":4544},{},[4545],{"type":26,"value":4429},{"type":21,"tag":870,"props":4547,"children":4548},{},[4549,4554],{"type":21,"tag":892,"props":4550,"children":4551},{},[4552],{"type":26,"value":4553},"描画",{"type":21,"tag":892,"props":4555,"children":4556},{},[4557],{"type":26,"value":4558},"HTML Canvas 2D",{"type":21,"tag":870,"props":4560,"children":4561},{},[4562,4566],{"type":21,"tag":892,"props":4563,"children":4564},{},[4565],{"type":26,"value":4430},{"type":21,"tag":892,"props":4567,"children":4568},{},[4569],{"type":26,"value":4570},"Noto Serif JP + 同梱フォント（力弱・行書・隷書）",{"type":21,"tag":65,"props":4572,"children":4574},{"id":4573},"なぜ-css-writing-mode-を選ばなかったのか",[4575,4577,4582],{"type":26,"value":4576},"なぜ CSS ",{"type":21,"tag":566,"props":4578,"children":4580},{"className":4579},[],[4581],{"type":26,"value":4492},{"type":26,"value":4583}," を選ばなかったのか",{"type":21,"tag":22,"props":4585,"children":4586},{},[4587],{"type":26,"value":4588},"詠み人は最終的に「1枚の PNG 画像」を生成するツールです。ユーザーはそれを Twitter/X にシェアしたり、画像としてダウンロードしたりします。",{"type":21,"tag":22,"props":4590,"children":4591},{},[4592,4594,4599],{"type":26,"value":4593},"「",{"type":21,"tag":140,"props":4595,"children":4596},{},[4597],{"type":26,"value":4598},"実装者が描画の全てをコントロールしたい",{"type":26,"value":4600},"」という要望にいちばん素直に応えてくれたのが、Canvas で1文字ずつ fillText する方式でした。後々背景を追加するなどを考えた際にも Canvas は扱いやすいと考えました。",{"type":21,"tag":65,"props":4602,"children":4604},{"id":4603},"基本のカラムレイアウト",[4605],{"type":26,"value":4603},{"type":21,"tag":22,"props":4607,"children":4608},{},[4609],{"type":26,"value":4610},"縦書きは「右から左へ」行が並びます。詠み人ではこれを次のように実装しています。",{"type":21,"tag":561,"props":4612,"children":4616},{"className":4613,"code":4614,"language":4615,"meta":8,"style":8},"language-typescript shiki shiki-themes vitesse-dark","validLines.forEach((line, ci) => {\n  const x = poemStartX - ci * colW\n  const chars = [...line]\n  const fs = Math.min(46, Math.floor((H - 130) / Math.max(chars.length, 1)))\n  const lh = fs + 6\n\n  ctx.fillStyle = style.text\n  ctx.font = `400 ${fs}px ${fontFamily}`\n  ctx.textAlign = 'center'\n\n  chars.forEach((ch, ci2) => {\n    drawVerticalChar(ctx, ch, x, startY + ci2 * lh, fs, fontId)\n  })\n})\n","typescript",[4617],{"type":21,"tag":566,"props":4618,"children":4619},{"__ignoreMap":8},[4620,4668,4710,4740,4867,4898,4905,4940,5006,5041,5048,5094,5178,5186],{"type":21,"tag":1330,"props":4621,"children":4622},{"class":1332,"line":12},[4623,4628,4632,4637,4642,4646,4650,4655,4659,4663],{"type":21,"tag":1330,"props":4624,"children":4625},{"style":1893},[4626],{"type":26,"value":4627},"validLines",{"type":21,"tag":1330,"props":4629,"children":4630},{"style":1356},[4631],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4633,"children":4634},{"style":1415},[4635],{"type":26,"value":4636},"forEach",{"type":21,"tag":1330,"props":4638,"children":4639},{"style":1356},[4640],{"type":26,"value":4641},"((",{"type":21,"tag":1330,"props":4643,"children":4644},{"style":1893},[4645],{"type":26,"value":1332},{"type":21,"tag":1330,"props":4647,"children":4648},{"style":1356},[4649],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4651,"children":4652},{"style":1893},[4653],{"type":26,"value":4654}," ci",{"type":21,"tag":1330,"props":4656,"children":4657},{"style":1356},[4658],{"type":26,"value":2747},{"type":21,"tag":1330,"props":4660,"children":4661},{"style":1356},[4662],{"type":26,"value":1778},{"type":21,"tag":1330,"props":4664,"children":4665},{"style":1356},[4666],{"type":26,"value":4667}," {\n",{"type":21,"tag":1330,"props":4669,"children":4670},{"class":1332,"line":39},[4671,4676,4681,4685,4690,4695,4700,4705],{"type":21,"tag":1330,"props":4672,"children":4673},{"style":1336},[4674],{"type":26,"value":4675},"  const ",{"type":21,"tag":1330,"props":4677,"children":4678},{"style":1893},[4679],{"type":26,"value":4680},"x",{"type":21,"tag":1330,"props":4682,"children":4683},{"style":1356},[4684],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4686,"children":4687},{"style":1893},[4688],{"type":26,"value":4689}," poemStartX",{"type":21,"tag":1330,"props":4691,"children":4692},{"style":1336},[4693],{"type":26,"value":4694}," - ",{"type":21,"tag":1330,"props":4696,"children":4697},{"style":1893},[4698],{"type":26,"value":4699},"ci",{"type":21,"tag":1330,"props":4701,"children":4702},{"style":1336},[4703],{"type":26,"value":4704}," * ",{"type":21,"tag":1330,"props":4706,"children":4707},{"style":1893},[4708],{"type":26,"value":4709},"colW\n",{"type":21,"tag":1330,"props":4711,"children":4712},{"class":1332,"line":1195},[4713,4717,4722,4726,4731,4735],{"type":21,"tag":1330,"props":4714,"children":4715},{"style":1336},[4716],{"type":26,"value":4675},{"type":21,"tag":1330,"props":4718,"children":4719},{"style":1893},[4720],{"type":26,"value":4721},"chars",{"type":21,"tag":1330,"props":4723,"children":4724},{"style":1356},[4725],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4727,"children":4728},{"style":1356},[4729],{"type":26,"value":4730}," [...",{"type":21,"tag":1330,"props":4732,"children":4733},{"style":1893},[4734],{"type":26,"value":1332},{"type":21,"tag":1330,"props":4736,"children":4737},{"style":1356},[4738],{"type":26,"value":4739},"]\n",{"type":21,"tag":1330,"props":4741,"children":4742},{"class":1332,"line":1406},[4743,4747,4752,4756,4761,4765,4770,4774,4779,4783,4787,4791,4796,4800,4805,4809,4814,4818,4822,4827,4831,4836,4840,4844,4848,4854,4858,4862],{"type":21,"tag":1330,"props":4744,"children":4745},{"style":1336},[4746],{"type":26,"value":4675},{"type":21,"tag":1330,"props":4748,"children":4749},{"style":1893},[4750],{"type":26,"value":4751},"fs",{"type":21,"tag":1330,"props":4753,"children":4754},{"style":1356},[4755],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4757,"children":4758},{"style":1893},[4759],{"type":26,"value":4760}," Math",{"type":21,"tag":1330,"props":4762,"children":4763},{"style":1356},[4764],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4766,"children":4767},{"style":1415},[4768],{"type":26,"value":4769},"min",{"type":21,"tag":1330,"props":4771,"children":4772},{"style":1356},[4773],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4775,"children":4776},{"style":2374},[4777],{"type":26,"value":4778},"46",{"type":21,"tag":1330,"props":4780,"children":4781},{"style":1356},[4782],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4784,"children":4785},{"style":1893},[4786],{"type":26,"value":4760},{"type":21,"tag":1330,"props":4788,"children":4789},{"style":1356},[4790],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4792,"children":4793},{"style":1415},[4794],{"type":26,"value":4795},"floor",{"type":21,"tag":1330,"props":4797,"children":4798},{"style":1356},[4799],{"type":26,"value":4641},{"type":21,"tag":1330,"props":4801,"children":4802},{"style":1893},[4803],{"type":26,"value":4804},"H",{"type":21,"tag":1330,"props":4806,"children":4807},{"style":1336},[4808],{"type":26,"value":4694},{"type":21,"tag":1330,"props":4810,"children":4811},{"style":2374},[4812],{"type":26,"value":4813},"130",{"type":21,"tag":1330,"props":4815,"children":4816},{"style":1356},[4817],{"type":26,"value":2747},{"type":21,"tag":1330,"props":4819,"children":4820},{"style":1336},[4821],{"type":26,"value":3113},{"type":21,"tag":1330,"props":4823,"children":4824},{"style":1893},[4825],{"type":26,"value":4826},"Math",{"type":21,"tag":1330,"props":4828,"children":4829},{"style":1356},[4830],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4832,"children":4833},{"style":1415},[4834],{"type":26,"value":4835},"max",{"type":21,"tag":1330,"props":4837,"children":4838},{"style":1356},[4839],{"type":26,"value":1837},{"type":21,"tag":1330,"props":4841,"children":4842},{"style":1893},[4843],{"type":26,"value":4721},{"type":21,"tag":1330,"props":4845,"children":4846},{"style":1356},[4847],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4849,"children":4851},{"style":4850},"--shiki-default:#B8A965",[4852],{"type":26,"value":4853},"length",{"type":21,"tag":1330,"props":4855,"children":4856},{"style":1356},[4857],{"type":26,"value":1852},{"type":21,"tag":1330,"props":4859,"children":4860},{"style":2374},[4861],{"type":26,"value":3298},{"type":21,"tag":1330,"props":4863,"children":4864},{"style":1356},[4865],{"type":26,"value":4866},")))\n",{"type":21,"tag":1330,"props":4868,"children":4869},{"class":1332,"line":1426},[4870,4874,4879,4883,4888,4893],{"type":21,"tag":1330,"props":4871,"children":4872},{"style":1336},[4873],{"type":26,"value":4675},{"type":21,"tag":1330,"props":4875,"children":4876},{"style":1893},[4877],{"type":26,"value":4878},"lh",{"type":21,"tag":1330,"props":4880,"children":4881},{"style":1356},[4882],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4884,"children":4885},{"style":1893},[4886],{"type":26,"value":4887}," fs",{"type":21,"tag":1330,"props":4889,"children":4890},{"style":1336},[4891],{"type":26,"value":4892}," + ",{"type":21,"tag":1330,"props":4894,"children":4895},{"style":2374},[4896],{"type":26,"value":4897},"6\n",{"type":21,"tag":1330,"props":4899,"children":4900},{"class":1332,"line":1436},[4901],{"type":21,"tag":1330,"props":4902,"children":4903},{"emptyLinePlaceholder":1430},[4904],{"type":26,"value":1433},{"type":21,"tag":1330,"props":4906,"children":4907},{"class":1332,"line":1473},[4908,4913,4917,4922,4926,4931,4935],{"type":21,"tag":1330,"props":4909,"children":4910},{"style":1893},[4911],{"type":26,"value":4912},"  ctx",{"type":21,"tag":1330,"props":4914,"children":4915},{"style":1356},[4916],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4918,"children":4919},{"style":1893},[4920],{"type":26,"value":4921},"fillStyle",{"type":21,"tag":1330,"props":4923,"children":4924},{"style":1356},[4925],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4927,"children":4928},{"style":1893},[4929],{"type":26,"value":4930}," style",{"type":21,"tag":1330,"props":4932,"children":4933},{"style":1356},[4934],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4936,"children":4937},{"style":1893},[4938],{"type":26,"value":4939},"text\n",{"type":21,"tag":1330,"props":4941,"children":4942},{"class":1332,"line":1490},[4943,4947,4951,4956,4960,4965,4970,4975,4979,4983,4988,4992,4997,5001],{"type":21,"tag":1330,"props":4944,"children":4945},{"style":1893},[4946],{"type":26,"value":4912},{"type":21,"tag":1330,"props":4948,"children":4949},{"style":1356},[4950],{"type":26,"value":2003},{"type":21,"tag":1330,"props":4952,"children":4953},{"style":1893},[4954],{"type":26,"value":4955},"font",{"type":21,"tag":1330,"props":4957,"children":4958},{"style":1356},[4959],{"type":26,"value":1901},{"type":21,"tag":1330,"props":4961,"children":4962},{"style":1781},[4963],{"type":26,"value":4964}," `",{"type":21,"tag":1330,"props":4966,"children":4967},{"style":1787},[4968],{"type":26,"value":4969},"400 ",{"type":21,"tag":1330,"props":4971,"children":4972},{"style":1376},[4973],{"type":26,"value":4974},"${",{"type":21,"tag":1330,"props":4976,"children":4977},{"style":1787},[4978],{"type":26,"value":4751},{"type":21,"tag":1330,"props":4980,"children":4981},{"style":1376},[4982],{"type":26,"value":1805},{"type":21,"tag":1330,"props":4984,"children":4985},{"style":1787},[4986],{"type":26,"value":4987},"px ",{"type":21,"tag":1330,"props":4989,"children":4990},{"style":1376},[4991],{"type":26,"value":4974},{"type":21,"tag":1330,"props":4993,"children":4994},{"style":1787},[4995],{"type":26,"value":4996},"fontFamily",{"type":21,"tag":1330,"props":4998,"children":4999},{"style":1376},[5000],{"type":26,"value":1805},{"type":21,"tag":1330,"props":5002,"children":5003},{"style":1781},[5004],{"type":26,"value":5005},"`\n",{"type":21,"tag":1330,"props":5007,"children":5008},{"class":1332,"line":1498},[5009,5013,5017,5022,5026,5031,5036],{"type":21,"tag":1330,"props":5010,"children":5011},{"style":1893},[5012],{"type":26,"value":4912},{"type":21,"tag":1330,"props":5014,"children":5015},{"style":1356},[5016],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5018,"children":5019},{"style":1893},[5020],{"type":26,"value":5021},"textAlign",{"type":21,"tag":1330,"props":5023,"children":5024},{"style":1356},[5025],{"type":26,"value":1901},{"type":21,"tag":1330,"props":5027,"children":5028},{"style":1781},[5029],{"type":26,"value":5030}," '",{"type":21,"tag":1330,"props":5032,"children":5033},{"style":1787},[5034],{"type":26,"value":5035},"center",{"type":21,"tag":1330,"props":5037,"children":5038},{"style":1781},[5039],{"type":26,"value":5040},"'\n",{"type":21,"tag":1330,"props":5042,"children":5043},{"class":1332,"line":1535},[5044],{"type":21,"tag":1330,"props":5045,"children":5046},{"emptyLinePlaceholder":1430},[5047],{"type":26,"value":1433},{"type":21,"tag":1330,"props":5049,"children":5050},{"class":1332,"line":1569},[5051,5056,5060,5064,5068,5073,5077,5082,5086,5090],{"type":21,"tag":1330,"props":5052,"children":5053},{"style":1893},[5054],{"type":26,"value":5055},"  chars",{"type":21,"tag":1330,"props":5057,"children":5058},{"style":1356},[5059],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5061,"children":5062},{"style":1415},[5063],{"type":26,"value":4636},{"type":21,"tag":1330,"props":5065,"children":5066},{"style":1356},[5067],{"type":26,"value":4641},{"type":21,"tag":1330,"props":5069,"children":5070},{"style":1893},[5071],{"type":26,"value":5072},"ch",{"type":21,"tag":1330,"props":5074,"children":5075},{"style":1356},[5076],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5078,"children":5079},{"style":1893},[5080],{"type":26,"value":5081}," ci2",{"type":21,"tag":1330,"props":5083,"children":5084},{"style":1356},[5085],{"type":26,"value":2747},{"type":21,"tag":1330,"props":5087,"children":5088},{"style":1356},[5089],{"type":26,"value":1778},{"type":21,"tag":1330,"props":5091,"children":5092},{"style":1356},[5093],{"type":26,"value":4667},{"type":21,"tag":1330,"props":5095,"children":5096},{"class":1332,"line":1912},[5097,5102,5106,5111,5115,5120,5124,5129,5133,5138,5143,5147,5152,5157,5161,5165,5169,5174],{"type":21,"tag":1330,"props":5098,"children":5099},{"style":1415},[5100],{"type":26,"value":5101},"    drawVerticalChar",{"type":21,"tag":1330,"props":5103,"children":5104},{"style":1356},[5105],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5107,"children":5108},{"style":1893},[5109],{"type":26,"value":5110},"ctx",{"type":21,"tag":1330,"props":5112,"children":5113},{"style":1356},[5114],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5116,"children":5117},{"style":1893},[5118],{"type":26,"value":5119}," ch",{"type":21,"tag":1330,"props":5121,"children":5122},{"style":1356},[5123],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5125,"children":5126},{"style":1893},[5127],{"type":26,"value":5128}," x",{"type":21,"tag":1330,"props":5130,"children":5131},{"style":1356},[5132],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5134,"children":5135},{"style":1893},[5136],{"type":26,"value":5137}," startY",{"type":21,"tag":1330,"props":5139,"children":5140},{"style":1336},[5141],{"type":26,"value":5142}," +",{"type":21,"tag":1330,"props":5144,"children":5145},{"style":1893},[5146],{"type":26,"value":5081},{"type":21,"tag":1330,"props":5148,"children":5149},{"style":1336},[5150],{"type":26,"value":5151}," *",{"type":21,"tag":1330,"props":5153,"children":5154},{"style":1893},[5155],{"type":26,"value":5156}," lh",{"type":21,"tag":1330,"props":5158,"children":5159},{"style":1356},[5160],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5162,"children":5163},{"style":1893},[5164],{"type":26,"value":4887},{"type":21,"tag":1330,"props":5166,"children":5167},{"style":1356},[5168],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5170,"children":5171},{"style":1893},[5172],{"type":26,"value":5173}," fontId",{"type":21,"tag":1330,"props":5175,"children":5176},{"style":1356},[5177],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5179,"children":5180},{"class":1332,"line":1933},[5181],{"type":21,"tag":1330,"props":5182,"children":5183},{"style":1356},[5184],{"type":26,"value":5185},"  })\n",{"type":21,"tag":1330,"props":5187,"children":5188},{"class":1332,"line":1954},[5189],{"type":21,"tag":1330,"props":5190,"children":5191},{"style":1356},[5192],{"type":26,"value":5193},"})\n",{"type":21,"tag":22,"props":5195,"children":5196},{},[5197,5199,5205,5207,5212],{"type":26,"value":5198},"行ごとに x座標を ",{"type":21,"tag":566,"props":5200,"children":5202},{"className":5201},[],[5203],{"type":26,"value":5204},"colW",{"type":26,"value":5206}," だけ左にずらしていくのが「右から左へ」の実現です。各列の中では、文字を上から下に ",{"type":21,"tag":566,"props":5208,"children":5210},{"className":5209},[],[5211],{"type":26,"value":4878},{"type":26,"value":5213},"（= フォントサイズ + 6px）の間隔で配置していきます。",{"type":21,"tag":22,"props":5215,"children":5216},{},[5217,5219,5225,5227,5232],{"type":26,"value":5218},"行の文字数に応じてフォントサイズを ",{"type":21,"tag":566,"props":5220,"children":5222},{"className":5221},[],[5223],{"type":26,"value":5224},"Math.floor((H - 130) / chars.length)",{"type":26,"value":5226}," で動的に決めているのもポイントです。長い句でも必ずキャンバス内に収まるように、",{"type":21,"tag":140,"props":5228,"children":5229},{},[5230],{"type":26,"value":5231},"フォントサイズのほうを縮める",{"type":26,"value":5233}," 方針にしました。",{"type":21,"tag":65,"props":5235,"children":5237},{"id":5236},"特殊文字の扱い-詠み人最大のハマりどころ",[5238],{"type":26,"value":5239},"特殊文字の扱い — 詠み人最大のハマりどころ",{"type":21,"tag":22,"props":5241,"children":5242},{},[5243,5245,5250,5252,5258],{"type":26,"value":5244},"縦書き日本語組版で一筋縄でいかないのが、",{"type":21,"tag":140,"props":5246,"children":5247},{},[5248],{"type":26,"value":5249},"句読点・括弧・長音符",{"type":26,"value":5251}," などの特殊文字です。これらを素直に ",{"type":21,"tag":566,"props":5253,"children":5255},{"className":5254},[],[5256],{"type":26,"value":5257},"fillText",{"type":26,"value":5259}," すると、向きや位置が崩れて読めない画像になります。",{"type":21,"tag":22,"props":5261,"children":5262},{},[5263,5265,5271],{"type":26,"value":5264},"詠み人では ",{"type":21,"tag":566,"props":5266,"children":5268},{"className":5267},[],[5269],{"type":26,"value":5270},"drawVerticalChar",{"type":26,"value":5272}," の中でこれらを個別処理しています。",{"type":21,"tag":561,"props":5274,"children":5276},{"className":4613,"code":5275,"language":4615,"meta":8,"style":8},"const ROTATE_CHARS = new Set('ー〜「」『』…・（）')\nconst TOP_RIGHT_CHARS = new Set('、。')\n\nfunction drawVerticalChar(ctx, ch, x, y, fontSize, fontId) {\n  if (TOP_RIGHT_CHARS.has(ch)) {\n    // 句読点: 右上に小さめに配置\n    ctx.save()\n    const size = fontSize * 0.5\n    ctx.font = ctx.font.replace(/\\d+px/, `${size}px`)\n    ctx.fillText(ch, x + fontSize * 0.35, y - fontSize * 0.3)\n    ctx.restore()\n  } else if (ROTATE_CHARS.has(ch)) {\n    // 長音・括弧等: 90度回転\n    ctx.save()\n    ctx.translate(x, y - fontSize * 0.35)\n    ctx.rotate(Math.PI / 2)\n    ctx.fillText(ch, 0, 0)\n    ctx.restore()\n  } else {\n    ctx.fillText(ch, x, y)\n  }\n}\n",[5277],{"type":21,"tag":566,"props":5278,"children":5279},{"__ignoreMap":8},[5280,5329,5374,5381,5452,5494,5502,5523,5553,5657,5734,5754,5804,5812,5831,5883,5930,5973,5992,6007,6050,6058],{"type":21,"tag":1330,"props":5281,"children":5282},{"class":1332,"line":12},[5283,5288,5293,5297,5302,5307,5311,5316,5321,5325],{"type":21,"tag":1330,"props":5284,"children":5285},{"style":1336},[5286],{"type":26,"value":5287},"const ",{"type":21,"tag":1330,"props":5289,"children":5290},{"style":1893},[5291],{"type":26,"value":5292},"ROTATE_CHARS",{"type":21,"tag":1330,"props":5294,"children":5295},{"style":1356},[5296],{"type":26,"value":1901},{"type":21,"tag":1330,"props":5298,"children":5299},{"style":1336},[5300],{"type":26,"value":5301}," new ",{"type":21,"tag":1330,"props":5303,"children":5304},{"style":1415},[5305],{"type":26,"value":5306},"Set",{"type":21,"tag":1330,"props":5308,"children":5309},{"style":1356},[5310],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5312,"children":5313},{"style":1781},[5314],{"type":26,"value":5315},"'",{"type":21,"tag":1330,"props":5317,"children":5318},{"style":1787},[5319],{"type":26,"value":5320},"ー〜「」『』…・（）",{"type":21,"tag":1330,"props":5322,"children":5323},{"style":1781},[5324],{"type":26,"value":5315},{"type":21,"tag":1330,"props":5326,"children":5327},{"style":1356},[5328],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5330,"children":5331},{"class":1332,"line":39},[5332,5336,5341,5345,5349,5353,5357,5361,5366,5370],{"type":21,"tag":1330,"props":5333,"children":5334},{"style":1336},[5335],{"type":26,"value":5287},{"type":21,"tag":1330,"props":5337,"children":5338},{"style":1893},[5339],{"type":26,"value":5340},"TOP_RIGHT_CHARS",{"type":21,"tag":1330,"props":5342,"children":5343},{"style":1356},[5344],{"type":26,"value":1901},{"type":21,"tag":1330,"props":5346,"children":5347},{"style":1336},[5348],{"type":26,"value":5301},{"type":21,"tag":1330,"props":5350,"children":5351},{"style":1415},[5352],{"type":26,"value":5306},{"type":21,"tag":1330,"props":5354,"children":5355},{"style":1356},[5356],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5358,"children":5359},{"style":1781},[5360],{"type":26,"value":5315},{"type":21,"tag":1330,"props":5362,"children":5363},{"style":1787},[5364],{"type":26,"value":5365},"、。",{"type":21,"tag":1330,"props":5367,"children":5368},{"style":1781},[5369],{"type":26,"value":5315},{"type":21,"tag":1330,"props":5371,"children":5372},{"style":1356},[5373],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5375,"children":5376},{"class":1332,"line":1195},[5377],{"type":21,"tag":1330,"props":5378,"children":5379},{"emptyLinePlaceholder":1430},[5380],{"type":26,"value":1433},{"type":21,"tag":1330,"props":5382,"children":5383},{"class":1332,"line":1406},[5384,5389,5394,5398,5402,5406,5410,5414,5418,5422,5427,5431,5436,5440,5444,5448],{"type":21,"tag":1330,"props":5385,"children":5386},{"style":1336},[5387],{"type":26,"value":5388},"function",{"type":21,"tag":1330,"props":5390,"children":5391},{"style":1415},[5392],{"type":26,"value":5393}," drawVerticalChar",{"type":21,"tag":1330,"props":5395,"children":5396},{"style":1356},[5397],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5399,"children":5400},{"style":1893},[5401],{"type":26,"value":5110},{"type":21,"tag":1330,"props":5403,"children":5404},{"style":1356},[5405],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5407,"children":5408},{"style":1893},[5409],{"type":26,"value":5119},{"type":21,"tag":1330,"props":5411,"children":5412},{"style":1356},[5413],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5415,"children":5416},{"style":1893},[5417],{"type":26,"value":5128},{"type":21,"tag":1330,"props":5419,"children":5420},{"style":1356},[5421],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5423,"children":5424},{"style":1893},[5425],{"type":26,"value":5426}," y",{"type":21,"tag":1330,"props":5428,"children":5429},{"style":1356},[5430],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5432,"children":5433},{"style":1893},[5434],{"type":26,"value":5435}," fontSize",{"type":21,"tag":1330,"props":5437,"children":5438},{"style":1356},[5439],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5441,"children":5442},{"style":1893},[5443],{"type":26,"value":5173},{"type":21,"tag":1330,"props":5445,"children":5446},{"style":1356},[5447],{"type":26,"value":2747},{"type":21,"tag":1330,"props":5449,"children":5450},{"style":1356},[5451],{"type":26,"value":4667},{"type":21,"tag":1330,"props":5453,"children":5454},{"class":1332,"line":1426},[5455,5460,5464,5468,5472,5477,5481,5485,5490],{"type":21,"tag":1330,"props":5456,"children":5457},{"style":1376},[5458],{"type":26,"value":5459},"  if",{"type":21,"tag":1330,"props":5461,"children":5462},{"style":1356},[5463],{"type":26,"value":2732},{"type":21,"tag":1330,"props":5465,"children":5466},{"style":1893},[5467],{"type":26,"value":5340},{"type":21,"tag":1330,"props":5469,"children":5470},{"style":1356},[5471],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5473,"children":5474},{"style":1415},[5475],{"type":26,"value":5476},"has",{"type":21,"tag":1330,"props":5478,"children":5479},{"style":1356},[5480],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5482,"children":5483},{"style":1893},[5484],{"type":26,"value":5072},{"type":21,"tag":1330,"props":5486,"children":5487},{"style":1356},[5488],{"type":26,"value":5489},"))",{"type":21,"tag":1330,"props":5491,"children":5492},{"style":1356},[5493],{"type":26,"value":4667},{"type":21,"tag":1330,"props":5495,"children":5496},{"class":1332,"line":1436},[5497],{"type":21,"tag":1330,"props":5498,"children":5499},{"style":1365},[5500],{"type":26,"value":5501},"    // 句読点: 右上に小さめに配置\n",{"type":21,"tag":1330,"props":5503,"children":5504},{"class":1332,"line":1473},[5505,5510,5514,5519],{"type":21,"tag":1330,"props":5506,"children":5507},{"style":1893},[5508],{"type":26,"value":5509},"    ctx",{"type":21,"tag":1330,"props":5511,"children":5512},{"style":1356},[5513],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5515,"children":5516},{"style":1415},[5517],{"type":26,"value":5518},"save",{"type":21,"tag":1330,"props":5520,"children":5521},{"style":1356},[5522],{"type":26,"value":2710},{"type":21,"tag":1330,"props":5524,"children":5525},{"class":1332,"line":1490},[5526,5531,5536,5540,5544,5548],{"type":21,"tag":1330,"props":5527,"children":5528},{"style":1336},[5529],{"type":26,"value":5530},"    const ",{"type":21,"tag":1330,"props":5532,"children":5533},{"style":1893},[5534],{"type":26,"value":5535},"size",{"type":21,"tag":1330,"props":5537,"children":5538},{"style":1356},[5539],{"type":26,"value":1901},{"type":21,"tag":1330,"props":5541,"children":5542},{"style":1893},[5543],{"type":26,"value":5435},{"type":21,"tag":1330,"props":5545,"children":5546},{"style":1336},[5547],{"type":26,"value":4704},{"type":21,"tag":1330,"props":5549,"children":5550},{"style":2374},[5551],{"type":26,"value":5552},"0.5\n",{"type":21,"tag":1330,"props":5554,"children":5555},{"class":1332,"line":1498},[5556,5560,5564,5568,5572,5577,5581,5585,5589,5594,5598,5603,5609,5614,5620,5624,5628,5632,5636,5640,5644,5648,5653],{"type":21,"tag":1330,"props":5557,"children":5558},{"style":1893},[5559],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5561,"children":5562},{"style":1356},[5563],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5565,"children":5566},{"style":1893},[5567],{"type":26,"value":4955},{"type":21,"tag":1330,"props":5569,"children":5570},{"style":1356},[5571],{"type":26,"value":1901},{"type":21,"tag":1330,"props":5573,"children":5574},{"style":1893},[5575],{"type":26,"value":5576}," ctx",{"type":21,"tag":1330,"props":5578,"children":5579},{"style":1356},[5580],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5582,"children":5583},{"style":1893},[5584],{"type":26,"value":4955},{"type":21,"tag":1330,"props":5586,"children":5587},{"style":1356},[5588],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5590,"children":5591},{"style":1415},[5592],{"type":26,"value":5593},"replace",{"type":21,"tag":1330,"props":5595,"children":5596},{"style":1356},[5597],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5599,"children":5600},{"style":1781},[5601],{"type":26,"value":5602},"/",{"type":21,"tag":1330,"props":5604,"children":5606},{"style":5605},"--shiki-default:#6872AB",[5607],{"type":26,"value":5608},"\\d",{"type":21,"tag":1330,"props":5610,"children":5611},{"style":2374},[5612],{"type":26,"value":5613},"+",{"type":21,"tag":1330,"props":5615,"children":5617},{"style":5616},"--shiki-default:#C4704F",[5618],{"type":26,"value":5619},"px",{"type":21,"tag":1330,"props":5621,"children":5622},{"style":1781},[5623],{"type":26,"value":5602},{"type":21,"tag":1330,"props":5625,"children":5626},{"style":1356},[5627],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5629,"children":5630},{"style":1781},[5631],{"type":26,"value":4964},{"type":21,"tag":1330,"props":5633,"children":5634},{"style":1376},[5635],{"type":26,"value":4974},{"type":21,"tag":1330,"props":5637,"children":5638},{"style":1787},[5639],{"type":26,"value":5535},{"type":21,"tag":1330,"props":5641,"children":5642},{"style":1376},[5643],{"type":26,"value":1805},{"type":21,"tag":1330,"props":5645,"children":5646},{"style":1787},[5647],{"type":26,"value":5619},{"type":21,"tag":1330,"props":5649,"children":5650},{"style":1781},[5651],{"type":26,"value":5652},"`",{"type":21,"tag":1330,"props":5654,"children":5655},{"style":1356},[5656],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5658,"children":5659},{"class":1332,"line":1535},[5660,5664,5668,5672,5676,5680,5684,5688,5692,5696,5700,5705,5709,5713,5717,5721,5725,5730],{"type":21,"tag":1330,"props":5661,"children":5662},{"style":1893},[5663],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5665,"children":5666},{"style":1356},[5667],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5669,"children":5670},{"style":1415},[5671],{"type":26,"value":5257},{"type":21,"tag":1330,"props":5673,"children":5674},{"style":1356},[5675],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5677,"children":5678},{"style":1893},[5679],{"type":26,"value":5072},{"type":21,"tag":1330,"props":5681,"children":5682},{"style":1356},[5683],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5685,"children":5686},{"style":1893},[5687],{"type":26,"value":5128},{"type":21,"tag":1330,"props":5689,"children":5690},{"style":1336},[5691],{"type":26,"value":5142},{"type":21,"tag":1330,"props":5693,"children":5694},{"style":1893},[5695],{"type":26,"value":5435},{"type":21,"tag":1330,"props":5697,"children":5698},{"style":1336},[5699],{"type":26,"value":5151},{"type":21,"tag":1330,"props":5701,"children":5702},{"style":2374},[5703],{"type":26,"value":5704}," 0.35",{"type":21,"tag":1330,"props":5706,"children":5707},{"style":1356},[5708],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5710,"children":5711},{"style":1893},[5712],{"type":26,"value":5426},{"type":21,"tag":1330,"props":5714,"children":5715},{"style":1336},[5716],{"type":26,"value":3293},{"type":21,"tag":1330,"props":5718,"children":5719},{"style":1893},[5720],{"type":26,"value":5435},{"type":21,"tag":1330,"props":5722,"children":5723},{"style":1336},[5724],{"type":26,"value":5151},{"type":21,"tag":1330,"props":5726,"children":5727},{"style":2374},[5728],{"type":26,"value":5729}," 0.3",{"type":21,"tag":1330,"props":5731,"children":5732},{"style":1356},[5733],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5735,"children":5736},{"class":1332,"line":1569},[5737,5741,5745,5750],{"type":21,"tag":1330,"props":5738,"children":5739},{"style":1893},[5740],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5742,"children":5743},{"style":1356},[5744],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5746,"children":5747},{"style":1415},[5748],{"type":26,"value":5749},"restore",{"type":21,"tag":1330,"props":5751,"children":5752},{"style":1356},[5753],{"type":26,"value":2710},{"type":21,"tag":1330,"props":5755,"children":5756},{"class":1332,"line":1912},[5757,5762,5767,5772,5776,5780,5784,5788,5792,5796,5800],{"type":21,"tag":1330,"props":5758,"children":5759},{"style":1356},[5760],{"type":26,"value":5761},"  }",{"type":21,"tag":1330,"props":5763,"children":5764},{"style":1376},[5765],{"type":26,"value":5766}," else",{"type":21,"tag":1330,"props":5768,"children":5769},{"style":1376},[5770],{"type":26,"value":5771}," if",{"type":21,"tag":1330,"props":5773,"children":5774},{"style":1356},[5775],{"type":26,"value":2732},{"type":21,"tag":1330,"props":5777,"children":5778},{"style":1893},[5779],{"type":26,"value":5292},{"type":21,"tag":1330,"props":5781,"children":5782},{"style":1356},[5783],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5785,"children":5786},{"style":1415},[5787],{"type":26,"value":5476},{"type":21,"tag":1330,"props":5789,"children":5790},{"style":1356},[5791],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5793,"children":5794},{"style":1893},[5795],{"type":26,"value":5072},{"type":21,"tag":1330,"props":5797,"children":5798},{"style":1356},[5799],{"type":26,"value":5489},{"type":21,"tag":1330,"props":5801,"children":5802},{"style":1356},[5803],{"type":26,"value":4667},{"type":21,"tag":1330,"props":5805,"children":5806},{"class":1332,"line":1933},[5807],{"type":21,"tag":1330,"props":5808,"children":5809},{"style":1365},[5810],{"type":26,"value":5811},"    // 長音・括弧等: 90度回転\n",{"type":21,"tag":1330,"props":5813,"children":5814},{"class":1332,"line":1954},[5815,5819,5823,5827],{"type":21,"tag":1330,"props":5816,"children":5817},{"style":1893},[5818],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5820,"children":5821},{"style":1356},[5822],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5824,"children":5825},{"style":1415},[5826],{"type":26,"value":5518},{"type":21,"tag":1330,"props":5828,"children":5829},{"style":1356},[5830],{"type":26,"value":2710},{"type":21,"tag":1330,"props":5832,"children":5833},{"class":1332,"line":1963},[5834,5838,5842,5847,5851,5855,5859,5863,5867,5871,5875,5879],{"type":21,"tag":1330,"props":5835,"children":5836},{"style":1893},[5837],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5839,"children":5840},{"style":1356},[5841],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5843,"children":5844},{"style":1415},[5845],{"type":26,"value":5846},"translate",{"type":21,"tag":1330,"props":5848,"children":5849},{"style":1356},[5850],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5852,"children":5853},{"style":1893},[5854],{"type":26,"value":4680},{"type":21,"tag":1330,"props":5856,"children":5857},{"style":1356},[5858],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5860,"children":5861},{"style":1893},[5862],{"type":26,"value":5426},{"type":21,"tag":1330,"props":5864,"children":5865},{"style":1336},[5866],{"type":26,"value":3293},{"type":21,"tag":1330,"props":5868,"children":5869},{"style":1893},[5870],{"type":26,"value":5435},{"type":21,"tag":1330,"props":5872,"children":5873},{"style":1336},[5874],{"type":26,"value":5151},{"type":21,"tag":1330,"props":5876,"children":5877},{"style":2374},[5878],{"type":26,"value":5704},{"type":21,"tag":1330,"props":5880,"children":5881},{"style":1356},[5882],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5884,"children":5885},{"class":1332,"line":1971},[5886,5890,5894,5899,5903,5907,5911,5916,5921,5926],{"type":21,"tag":1330,"props":5887,"children":5888},{"style":1893},[5889],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5891,"children":5892},{"style":1356},[5893],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5895,"children":5896},{"style":1415},[5897],{"type":26,"value":5898},"rotate",{"type":21,"tag":1330,"props":5900,"children":5901},{"style":1356},[5902],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5904,"children":5905},{"style":1893},[5906],{"type":26,"value":4826},{"type":21,"tag":1330,"props":5908,"children":5909},{"style":1356},[5910],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5912,"children":5913},{"style":1893},[5914],{"type":26,"value":5915},"PI",{"type":21,"tag":1330,"props":5917,"children":5918},{"style":1336},[5919],{"type":26,"value":5920}," /",{"type":21,"tag":1330,"props":5922,"children":5923},{"style":2374},[5924],{"type":26,"value":5925}," 2",{"type":21,"tag":1330,"props":5927,"children":5928},{"style":1356},[5929],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5931,"children":5932},{"class":1332,"line":2032},[5933,5937,5941,5945,5949,5953,5957,5961,5965,5969],{"type":21,"tag":1330,"props":5934,"children":5935},{"style":1893},[5936],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5938,"children":5939},{"style":1356},[5940],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5942,"children":5943},{"style":1415},[5944],{"type":26,"value":5257},{"type":21,"tag":1330,"props":5946,"children":5947},{"style":1356},[5948],{"type":26,"value":1837},{"type":21,"tag":1330,"props":5950,"children":5951},{"style":1893},[5952],{"type":26,"value":5072},{"type":21,"tag":1330,"props":5954,"children":5955},{"style":1356},[5956],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5958,"children":5959},{"style":2374},[5960],{"type":26,"value":2466},{"type":21,"tag":1330,"props":5962,"children":5963},{"style":1356},[5964],{"type":26,"value":1852},{"type":21,"tag":1330,"props":5966,"children":5967},{"style":2374},[5968],{"type":26,"value":2466},{"type":21,"tag":1330,"props":5970,"children":5971},{"style":1356},[5972],{"type":26,"value":1879},{"type":21,"tag":1330,"props":5974,"children":5975},{"class":1332,"line":2089},[5976,5980,5984,5988],{"type":21,"tag":1330,"props":5977,"children":5978},{"style":1893},[5979],{"type":26,"value":5509},{"type":21,"tag":1330,"props":5981,"children":5982},{"style":1356},[5983],{"type":26,"value":2003},{"type":21,"tag":1330,"props":5985,"children":5986},{"style":1415},[5987],{"type":26,"value":5749},{"type":21,"tag":1330,"props":5989,"children":5990},{"style":1356},[5991],{"type":26,"value":2710},{"type":21,"tag":1330,"props":5993,"children":5994},{"class":1332,"line":2563},[5995,5999,6003],{"type":21,"tag":1330,"props":5996,"children":5997},{"style":1356},[5998],{"type":26,"value":5761},{"type":21,"tag":1330,"props":6000,"children":6001},{"style":1376},[6002],{"type":26,"value":5766},{"type":21,"tag":1330,"props":6004,"children":6005},{"style":1356},[6006],{"type":26,"value":4667},{"type":21,"tag":1330,"props":6008,"children":6009},{"class":1332,"line":2584},[6010,6014,6018,6022,6026,6030,6034,6038,6042,6046],{"type":21,"tag":1330,"props":6011,"children":6012},{"style":1893},[6013],{"type":26,"value":5509},{"type":21,"tag":1330,"props":6015,"children":6016},{"style":1356},[6017],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6019,"children":6020},{"style":1415},[6021],{"type":26,"value":5257},{"type":21,"tag":1330,"props":6023,"children":6024},{"style":1356},[6025],{"type":26,"value":1837},{"type":21,"tag":1330,"props":6027,"children":6028},{"style":1893},[6029],{"type":26,"value":5072},{"type":21,"tag":1330,"props":6031,"children":6032},{"style":1356},[6033],{"type":26,"value":1852},{"type":21,"tag":1330,"props":6035,"children":6036},{"style":1893},[6037],{"type":26,"value":5128},{"type":21,"tag":1330,"props":6039,"children":6040},{"style":1356},[6041],{"type":26,"value":1852},{"type":21,"tag":1330,"props":6043,"children":6044},{"style":1893},[6045],{"type":26,"value":5426},{"type":21,"tag":1330,"props":6047,"children":6048},{"style":1356},[6049],{"type":26,"value":1879},{"type":21,"tag":1330,"props":6051,"children":6052},{"class":1332,"line":2615},[6053],{"type":21,"tag":1330,"props":6054,"children":6055},{"style":1356},[6056],{"type":26,"value":6057},"  }\n",{"type":21,"tag":1330,"props":6059,"children":6060},{"class":1332,"line":2637},[6061],{"type":21,"tag":1330,"props":6062,"children":6063},{"style":1356},[6064],{"type":26,"value":1575},{"type":21,"tag":350,"props":6066,"children":6068},{"id":6067},"句読点は右上に半分サイズで置く",[6069],{"type":26,"value":6067},{"type":21,"tag":22,"props":6071,"children":6072},{},[6073,6075,6080,6082,6088],{"type":26,"value":6074},"日本の伝統的な縦書き組版では、「、」と「。」は ",{"type":21,"tag":140,"props":6076,"children":6077},{},[6078],{"type":26,"value":6079},"マス目の右上に小さく",{"type":26,"value":6081}," 配置します。詠み人ではサイズを 50%、座標を ",{"type":21,"tag":566,"props":6083,"children":6085},{"className":6084},[],[6086],{"type":26,"value":6087},"(x + fontSize * 0.35, y - fontSize * 0.3)",{"type":26,"value":6089}," に補正することで、この配置を再現しました。",{"type":21,"tag":350,"props":6091,"children":6093},{"id":6092},"長音括弧は90度回転",[6094],{"type":26,"value":6095},"長音・括弧は90度回転",{"type":21,"tag":22,"props":6097,"children":6098},{},[6099,6101,6107,6109,6114,6116,6121],{"type":26,"value":6100},"「ー」「〜」「「」「（）」などは、横書きでそのまま描くと縦書きの文脈で読みにくくなります。詠み人では ",{"type":21,"tag":566,"props":6102,"children":6104},{"className":6103},[],[6105],{"type":26,"value":6106},"ctx.rotate(Math.PI / 2)",{"type":26,"value":6108}," で ",{"type":21,"tag":140,"props":6110,"children":6111},{},[6112],{"type":26,"value":6113},"90度時計回りに回転",{"type":26,"value":6115}," して描画しています。",{"type":21,"tag":566,"props":6117,"children":6119},{"className":6118},[],[6120],{"type":26,"value":5846},{"type":26,"value":6122}," で座標を中心にずらしてから回転させるのがコツです。",{"type":21,"tag":22,"props":6124,"children":6125},{},[6126,6128,6133],{"type":26,"value":6127},"このあたりは日本語組版の JIS X 4051 や W3C の日本語組版要件を全部実装しようとすると際限がないので、",{"type":21,"tag":140,"props":6129,"children":6130},{},[6131],{"type":26,"value":6132},"詠み人に実際に入力される文字種に限って割り切る",{"type":26,"value":6134}," 方針を取りました。",{"type":21,"tag":350,"props":6136,"children":6138},{"id":6137},"フォントによって位置が異なる",[6139],{"type":26,"value":6137},{"type":21,"tag":22,"props":6141,"children":6142},{},[6143,6145,6150],{"type":26,"value":6144},"回転処理をさらに厄介にするのが、",{"type":21,"tag":140,"props":6146,"children":6147},{},[6148],{"type":26,"value":6149},"フォントごとにグリフの中心位置が微妙にずれる",{"type":26,"value":6151}," という問題です。詠み人では明朝（Noto Serif JP）・力弱（851CHIKARA-YOWAKU）・行書（AoyagiKouzanT）・隷書（AoyagiReisyosimo）の4フォントを選択できますが、このうち隷書だけは回転文字の描画位置がずれます。",{"type":21,"tag":22,"props":6153,"children":6154},{},[6155],{"type":26,"value":6156},"実装ではフォント ID を見て隷書のときだけ X 軸方向にオフセットを入れています。",{"type":21,"tag":561,"props":6158,"children":6160},{"className":4613,"code":6159,"language":4615,"meta":8,"style":8},"const offsetX = fontId === 'reisyo' ? -fontSize * 0.15 : 0\nctx.translate(x + offsetX, y - fontSize * 0.35)\nctx.rotate(Math.PI / 2)\n",[6161],{"type":21,"tag":566,"props":6162,"children":6163},{"__ignoreMap":8},[6164,6231,6291],{"type":21,"tag":1330,"props":6165,"children":6166},{"class":1332,"line":12},[6167,6171,6176,6180,6184,6189,6193,6198,6202,6207,6212,6216,6221,6226],{"type":21,"tag":1330,"props":6168,"children":6169},{"style":1336},[6170],{"type":26,"value":5287},{"type":21,"tag":1330,"props":6172,"children":6173},{"style":1893},[6174],{"type":26,"value":6175},"offsetX",{"type":21,"tag":1330,"props":6177,"children":6178},{"style":1356},[6179],{"type":26,"value":1901},{"type":21,"tag":1330,"props":6181,"children":6182},{"style":1893},[6183],{"type":26,"value":5173},{"type":21,"tag":1330,"props":6185,"children":6186},{"style":1336},[6187],{"type":26,"value":6188}," === ",{"type":21,"tag":1330,"props":6190,"children":6191},{"style":1781},[6192],{"type":26,"value":5315},{"type":21,"tag":1330,"props":6194,"children":6195},{"style":1787},[6196],{"type":26,"value":6197},"reisyo",{"type":21,"tag":1330,"props":6199,"children":6200},{"style":1781},[6201],{"type":26,"value":5315},{"type":21,"tag":1330,"props":6203,"children":6204},{"style":1336},[6205],{"type":26,"value":6206}," ? -",{"type":21,"tag":1330,"props":6208,"children":6209},{"style":1893},[6210],{"type":26,"value":6211},"fontSize",{"type":21,"tag":1330,"props":6213,"children":6214},{"style":1336},[6215],{"type":26,"value":4704},{"type":21,"tag":1330,"props":6217,"children":6218},{"style":2374},[6219],{"type":26,"value":6220},"0.15",{"type":21,"tag":1330,"props":6222,"children":6223},{"style":1336},[6224],{"type":26,"value":6225}," : ",{"type":21,"tag":1330,"props":6227,"children":6228},{"style":2374},[6229],{"type":26,"value":6230},"0\n",{"type":21,"tag":1330,"props":6232,"children":6233},{"class":1332,"line":39},[6234,6238,6242,6246,6250,6254,6258,6263,6267,6271,6275,6279,6283,6287],{"type":21,"tag":1330,"props":6235,"children":6236},{"style":1893},[6237],{"type":26,"value":5110},{"type":21,"tag":1330,"props":6239,"children":6240},{"style":1356},[6241],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6243,"children":6244},{"style":1415},[6245],{"type":26,"value":5846},{"type":21,"tag":1330,"props":6247,"children":6248},{"style":1356},[6249],{"type":26,"value":1837},{"type":21,"tag":1330,"props":6251,"children":6252},{"style":1893},[6253],{"type":26,"value":4680},{"type":21,"tag":1330,"props":6255,"children":6256},{"style":1336},[6257],{"type":26,"value":5142},{"type":21,"tag":1330,"props":6259,"children":6260},{"style":1893},[6261],{"type":26,"value":6262}," offsetX",{"type":21,"tag":1330,"props":6264,"children":6265},{"style":1356},[6266],{"type":26,"value":1852},{"type":21,"tag":1330,"props":6268,"children":6269},{"style":1893},[6270],{"type":26,"value":5426},{"type":21,"tag":1330,"props":6272,"children":6273},{"style":1336},[6274],{"type":26,"value":3293},{"type":21,"tag":1330,"props":6276,"children":6277},{"style":1893},[6278],{"type":26,"value":5435},{"type":21,"tag":1330,"props":6280,"children":6281},{"style":1336},[6282],{"type":26,"value":5151},{"type":21,"tag":1330,"props":6284,"children":6285},{"style":2374},[6286],{"type":26,"value":5704},{"type":21,"tag":1330,"props":6288,"children":6289},{"style":1356},[6290],{"type":26,"value":1879},{"type":21,"tag":1330,"props":6292,"children":6293},{"class":1332,"line":1195},[6294,6298,6302,6306,6310,6314,6318,6322,6326,6330],{"type":21,"tag":1330,"props":6295,"children":6296},{"style":1893},[6297],{"type":26,"value":5110},{"type":21,"tag":1330,"props":6299,"children":6300},{"style":1356},[6301],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6303,"children":6304},{"style":1415},[6305],{"type":26,"value":5898},{"type":21,"tag":1330,"props":6307,"children":6308},{"style":1356},[6309],{"type":26,"value":1837},{"type":21,"tag":1330,"props":6311,"children":6312},{"style":1893},[6313],{"type":26,"value":4826},{"type":21,"tag":1330,"props":6315,"children":6316},{"style":1356},[6317],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6319,"children":6320},{"style":1893},[6321],{"type":26,"value":5915},{"type":21,"tag":1330,"props":6323,"children":6324},{"style":1336},[6325],{"type":26,"value":5920},{"type":21,"tag":1330,"props":6327,"children":6328},{"style":2374},[6329],{"type":26,"value":5925},{"type":21,"tag":1330,"props":6331,"children":6332},{"style":1356},[6333],{"type":26,"value":1879},{"type":21,"tag":22,"props":6335,"children":6336},{},[6337,6343,6345,6351,6353,6358],{"type":21,"tag":566,"props":6338,"children":6340},{"className":6339},[],[6341],{"type":26,"value":6342},"-fontSize * 0.15",{"type":26,"value":6344}," という補正値は、隷書フォントの「ー」や「〜」を実際に描画しながら目視で合わせた数値です。フォント間でグリフメトリクスが統一されていないため、汎用的な計算式を導出するのは難しく、フォントを追加するたびにこの分岐が増える可能性があります。現状は4フォント固定なので ",{"type":21,"tag":566,"props":6346,"children":6348},{"className":6347},[],[6349],{"type":26,"value":6350},"fontId === 'reisyo'",{"type":26,"value":6352}," の1分岐で収まっていますが、フォント選択肢を増やす場合はオフセットのテーブル化が必要になるでしょう。私は ",{"type":21,"tag":140,"props":6354,"children":6355},{},[6356],{"type":26,"value":6357},"必要最低限で実装する",{"type":26,"value":6359}," という原則で実装を進めています。現時点で複雑にならないことが最優先です。",{"type":21,"tag":65,"props":6361,"children":6363},{"id":6362},"フォントの読み込み待機を忘れずに",[6364],{"type":26,"value":6362},{"type":21,"tag":22,"props":6366,"children":6367},{},[6368,6370,6375,6377,6382],{"type":26,"value":6369},"Canvas 描画で地味にハマるのが「",{"type":21,"tag":140,"props":6371,"children":6372},{},[6373],{"type":26,"value":6374},"指定したフォントがまだ読み込まれていないとデフォルトフォントで描画される",{"type":26,"value":6376},"」問題です。特に Google Fonts のような外部フォントを使う場合、初回描画時に ",{"type":21,"tag":566,"props":6378,"children":6380},{"className":6379},[],[6381],{"type":26,"value":5257},{"type":26,"value":6383}," が意図しないフォントで動き、2回目以降は正常、という不安定な挙動を起こします。",{"type":21,"tag":22,"props":6385,"children":6386},{},[6387,6389,6395],{"type":26,"value":6388},"詠み人では描画開始前に ",{"type":21,"tag":566,"props":6390,"children":6392},{"className":6391},[],[6393],{"type":26,"value":6394},"document.fonts.load()",{"type":26,"value":6396}," を明示的に待機しています。",{"type":21,"tag":561,"props":6398,"children":6400},{"className":4613,"code":6399,"language":4615,"meta":8,"style":8},"await document.fonts.load(`400 46px ${fontFamily}`)\nawait document.fonts.load(`300 13px ${fontFamily}`)\n",[6401],{"type":21,"tag":566,"props":6402,"children":6403},{"__ignoreMap":8},[6404,6468],{"type":21,"tag":1330,"props":6405,"children":6406},{"class":1332,"line":12},[6407,6412,6417,6421,6426,6430,6435,6439,6443,6448,6452,6456,6460,6464],{"type":21,"tag":1330,"props":6408,"children":6409},{"style":1376},[6410],{"type":26,"value":6411},"await",{"type":21,"tag":1330,"props":6413,"children":6414},{"style":1893},[6415],{"type":26,"value":6416}," document",{"type":21,"tag":1330,"props":6418,"children":6419},{"style":1356},[6420],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6422,"children":6423},{"style":1893},[6424],{"type":26,"value":6425},"fonts",{"type":21,"tag":1330,"props":6427,"children":6428},{"style":1356},[6429],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6431,"children":6432},{"style":1415},[6433],{"type":26,"value":6434},"load",{"type":21,"tag":1330,"props":6436,"children":6437},{"style":1356},[6438],{"type":26,"value":1837},{"type":21,"tag":1330,"props":6440,"children":6441},{"style":1781},[6442],{"type":26,"value":5652},{"type":21,"tag":1330,"props":6444,"children":6445},{"style":1787},[6446],{"type":26,"value":6447},"400 46px ",{"type":21,"tag":1330,"props":6449,"children":6450},{"style":1376},[6451],{"type":26,"value":4974},{"type":21,"tag":1330,"props":6453,"children":6454},{"style":1787},[6455],{"type":26,"value":4996},{"type":21,"tag":1330,"props":6457,"children":6458},{"style":1376},[6459],{"type":26,"value":1805},{"type":21,"tag":1330,"props":6461,"children":6462},{"style":1781},[6463],{"type":26,"value":5652},{"type":21,"tag":1330,"props":6465,"children":6466},{"style":1356},[6467],{"type":26,"value":1879},{"type":21,"tag":1330,"props":6469,"children":6470},{"class":1332,"line":39},[6471,6475,6479,6483,6487,6491,6495,6499,6503,6508,6512,6516,6520,6524],{"type":21,"tag":1330,"props":6472,"children":6473},{"style":1376},[6474],{"type":26,"value":6411},{"type":21,"tag":1330,"props":6476,"children":6477},{"style":1893},[6478],{"type":26,"value":6416},{"type":21,"tag":1330,"props":6480,"children":6481},{"style":1356},[6482],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6484,"children":6485},{"style":1893},[6486],{"type":26,"value":6425},{"type":21,"tag":1330,"props":6488,"children":6489},{"style":1356},[6490],{"type":26,"value":2003},{"type":21,"tag":1330,"props":6492,"children":6493},{"style":1415},[6494],{"type":26,"value":6434},{"type":21,"tag":1330,"props":6496,"children":6497},{"style":1356},[6498],{"type":26,"value":1837},{"type":21,"tag":1330,"props":6500,"children":6501},{"style":1781},[6502],{"type":26,"value":5652},{"type":21,"tag":1330,"props":6504,"children":6505},{"style":1787},[6506],{"type":26,"value":6507},"300 13px ",{"type":21,"tag":1330,"props":6509,"children":6510},{"style":1376},[6511],{"type":26,"value":4974},{"type":21,"tag":1330,"props":6513,"children":6514},{"style":1787},[6515],{"type":26,"value":4996},{"type":21,"tag":1330,"props":6517,"children":6518},{"style":1376},[6519],{"type":26,"value":1805},{"type":21,"tag":1330,"props":6521,"children":6522},{"style":1781},[6523],{"type":26,"value":5652},{"type":21,"tag":1330,"props":6525,"children":6526},{"style":1356},[6527],{"type":26,"value":1879},{"type":21,"tag":22,"props":6529,"children":6530},{},[6531,6533,6539,6541,6547],{"type":26,"value":6532},"本文用（46px）と作者名用（13px）の両方をロードしないと、作者名だけがデフォルトフォントになる、という微妙なバグが出ます。フォントウェイトが異なる（本文は ",{"type":21,"tag":566,"props":6534,"children":6536},{"className":6535},[],[6537],{"type":26,"value":6538},"400",{"type":26,"value":6540},"、作者名は ",{"type":21,"tag":566,"props":6542,"children":6544},{"className":6543},[],[6545],{"type":26,"value":6546},"300",{"type":26,"value":6548},"）ため、それぞれ個別に await しています。",{"type":21,"tag":65,"props":6550,"children":6551},{"id":4339},[6552],{"type":26,"value":4339},{"type":21,"tag":90,"props":6554,"children":6555},{},[6556,6568,6573,6585],{"type":21,"tag":94,"props":6557,"children":6558},{},[6559,6561,6566],{"type":26,"value":6560},"Canvas での縦書き実装は、",{"type":21,"tag":140,"props":6562,"children":6563},{},[6564],{"type":26,"value":6565},"画像生成ツール",{"type":26,"value":6567}," という用途では CSS より素直",{"type":21,"tag":94,"props":6569,"children":6570},{},[6571],{"type":26,"value":6572},"句読点は 50% サイズで右上配置、長音・括弧は 90° 回転、という特殊文字処理が必須",{"type":21,"tag":94,"props":6574,"children":6575},{},[6576,6578,6583],{"type":26,"value":6577},"日本語組版の全仕様を追うのではなく、",{"type":21,"tag":140,"props":6579,"children":6580},{},[6581],{"type":26,"value":6582},"自分のツールに実際に入力される文字",{"type":26,"value":6584}," に絞って割り切る",{"type":21,"tag":94,"props":6586,"children":6587},{},[6588,6593],{"type":21,"tag":566,"props":6589,"children":6591},{"className":6590},[],[6592],{"type":26,"value":6394},{"type":26,"value":6594}," を複数サイズで await することで、フォント未ロード起因のバグを回避",{"type":21,"tag":22,"props":6596,"children":6597},{},[6598],{"type":26,"value":6599},"縦書きは奥が深い世界ですが、画像生成用途に限れば Canvas で十分戦えます。",{"type":21,"tag":4402,"props":6601,"children":6602},{},[6603],{"type":26,"value":4406},{"title":8,"searchDepth":39,"depth":39,"links":6605},[6606,6607,6608,6610,6611,6616,6617],{"id":67,"depth":39,"text":67},{"id":4497,"depth":39,"text":4497},{"id":4573,"depth":39,"text":6609},"なぜ CSS writing-mode を選ばなかったのか",{"id":4603,"depth":39,"text":4603},{"id":5236,"depth":39,"text":5239,"children":6612},[6613,6614,6615],{"id":6067,"depth":1195,"text":6067},{"id":6092,"depth":1195,"text":6095},{"id":6137,"depth":1195,"text":6137},{"id":6362,"depth":39,"text":6362},{"id":4339,"depth":39,"text":4339},"content:articles:tech:development:yominchu-vertical-canvas.md","articles/tech/development/yominchu-vertical-canvas.md","articles/tech/development/yominchu-vertical-canvas",1776356302637]