CINXE.COM

ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました

<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><title>ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました</title><link rel="canonical" href="https://zenn.dev/ttskch/articles/1f1572cfd2e375"/><meta name="twitter:card" content="summary_large_image"/><meta property="og:url" content="https://zenn.dev/ttskch/articles/1f1572cfd2e375"/><meta property="og:title" content="ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました"/><meta property="og:image" content="https://res.cloudinary.com/zenn/image/upload/s--qDOTW7FI--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:%25E3%2581%25A4%25E3%2581%2584%25E3%2581%25AB%25E3%2580%2581Web%25E3%2582%25A2%25E3%2583%2597%25E3%2583%25AA%25E3%2581%25A7%25E3%2581%25AE%25E5%25B8%25B3%25E7%25A5%25A8%25E5%258D%25B0%25E5%2588%25B7%25E3%2581%25AE%25E3%2583%2599%25E3%2582%25B9%25E3%2583%2588%25E3%2583%2597%25E3%2583%25A9%25E3%2582%25AF%25E3%2583%2586%25E3%2582%25A3%25E3%2582%25B9%25E3%2582%2592%25E7%25B7%25A8%25E3%2581%25BF%25E5%2587%25BA%25E3%2581%2597%25E3%2581%25BE%25E3%2581%2597%25E3%2581%259F%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:ttskch%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2RhY2Y2ZmZjMDcuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png"/><meta property="og:type" content="article"/><meta property="og:site_name" content="Zenn"/><meta content="https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg" name="zenn:image"/><meta content="ttskchさんによる記事" name="zenn:description"/><meta name="next-head-count" content="12"/><script nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=">var displayMode = 'browser'; if (window.matchMedia('(display-mode: standalone)').matches) { displayMode = 'standalone'; // PWA } else if (window.navigator.standalone === true) { displayMode = 'standalone'; // PWA on iOS Safari } window.dataLayer = window.dataLayer || []; window.dataLayer.push({'display_mode': displayMode});</script><script nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=">(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-K42DRM8');</script><script async="" src="https://www.googletagmanager.com/gtag/js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0="></script><script nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=">window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date());</script><script src="https://embed.zenn.studio/js/listen-embed-event.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0="></script><style nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0="> @font-face { font-family: 'Inter'; font-weight: 600; font-display: swap; src: local(''), url('https://static.zenn.studio/fonts/inter-v3-latin-600.woff2') format('woff2'); } @font-face { font-family: 'Inter'; font-weight: 700; font-display: swap; src: local(''), url('https://static.zenn.studio/fonts/inter-v3-latin-700.woff2') format('woff2'); }</style><meta content="Zenn" name="apple-mobile-web-app-title"/><link href="/manifest.json" rel="manifest"/><link href="https://static.zenn.studio/images/logo-transparent.png" rel="shortcut icon" type="image/png"/><link href="https://static.zenn.studio/images/icon.png" rel="apple-touch-icon-precomposed" type="image/png"/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="preload" href="https://static.zenn.studio/_next/static/css/c98e2d4c357fbe9f.css" as="style"/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="stylesheet" href="https://static.zenn.studio/_next/static/css/c98e2d4c357fbe9f.css" data-n-g=""/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="preload" href="https://static.zenn.studio/_next/static/css/90c5120f4b791556.css" as="style"/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="stylesheet" href="https://static.zenn.studio/_next/static/css/90c5120f4b791556.css" data-n-p=""/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="preload" href="https://static.zenn.studio/_next/static/css/73149e3adcac36b7.css" as="style"/><link nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" rel="stylesheet" href="https://static.zenn.studio/_next/static/css/73149e3adcac36b7.css"/><noscript data-n-css="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0="></noscript><script defer="" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" nomodule="" src="https://static.zenn.studio/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script defer="" src="https://static.zenn.studio/_next/static/chunks/3322.059d327f5cb790e7.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0="></script><script src="https://static.zenn.studio/_next/static/chunks/webpack-b1e7249ff5e93c77.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/framework-ca706bf673a13738.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/main-e87375bda7b226b4.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/pages/_app-1ac98e4f6170e59b.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/6965-d0e146ae5afb1777.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/1828-6617a55e48357072.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/2001-b0dc4bec07955a59.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/9328-2d342e4ba7eccf14.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/840-47fb90d0d9531395.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/chunks/pages/%5Busername%5D/articles/%5Bslug%5D-36dec9385298ff41.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/6cWpQ5Z1LAtdbyaB4EE5X/_buildManifest.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script><script src="https://static.zenn.studio/_next/static/6cWpQ5Z1LAtdbyaB4EE5X/_ssgManifest.js" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=" defer=""></script></head><body><script nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=">const shouldUseTwemoji = !/(googlebot|macintosh|macintel|macppc|mac68k|macos|iphone|ipad)/i.test(window.navigator.userAgent); if(shouldUseTwemoji) document.body.setAttribute("data-use-twemoji", "true");</script><div id="__next"><header class="AppHeader_header__54XdE"><div class="Container_wide__ykGLh Container_common__figYY"><div class="AppHeader_inner__CJC4C"><a class="AppHeader_homeLink__cjUD6" href="/"><svg x="0px" y="0px" viewBox="0 0 377.4 88.3" aria-label="Zenn | エンジニアのための情報共有コミュニティ" height="22"><title>Zenn</title><g fill="#111"><path d="M233,56.8h-39c0.5,3.5,2.2,6.8,4.8,9.2c2.7,2.3,6.2,3.5,9.8,3.4c2.8,0,5.6-0.5,8.2-1.7c2.5-1.1,4.8-2.8,6.5-5l8.2,9.5 c-2.5,3.4-5.7,6.1-9.5,7.9c-4.6,2.2-9.6,3.3-14.7,3.2c-5.7,0.1-11.4-1.2-16.5-4c-4.5-2.5-8.2-6.3-10.7-10.9s-3.8-9.8-3.7-15.1v-2.2 c-0.1-5.7,1.1-11.3,3.5-16.5c2.2-4.7,5.7-8.6,10.1-11.3c4.7-2.8,10.1-4.2,15.5-4.1c5.2-0.1,10.3,1.1,14.9,3.7 c4.1,2.5,7.4,6.2,9.4,10.5c2.2,5.1,3.3,10.5,3.2,16.1V56.8z M216.1,43.9c0.1-2.9-0.9-5.7-2.8-7.9c-1.8-1.9-4.4-2.9-7.9-2.9 c-2.9-0.1-5.8,1.1-7.7,3.2c-2,2.6-3.3,5.7-3.6,9h22V43.9z"></path><path d="M128.3,67.9h36.1v14.7h-56.9V72l35.8-54.3h-36.2V2.9h56.6v10.4L128.3,67.9z"></path><path d="M248.8,50.7c0-19.1,12.7-29.2,28.2-29.2s27.9,10.1,27.9,29.2V82h-16V51.4c0-10.6-4.8-16.1-12-16.1s-12.4,5.5-12.4,16.1 v30.7h-15.8L248.8,50.7L248.8,50.7z"></path><path d="M320.3,50.7c0-19.1,12.7-29.2,28.2-29.2s27.9,10.1,27.9,29.2V82h-16V51.4c0-10.6-4.8-16.1-12-16.1S336,40.8,336,51.4v30.7 h-15.8L320.3,50.7L320.3,50.7z"></path></g><path fill="#3EA8FF" class="st0" d="M2.4,83.3h17c0.9,0,1.7-0.5,2.2-1.2L68.4,5.2C69,4.2,68.3,3,67.1,3H51c-0.8,0-1.5,0.4-1.9,1.1L1.6,81.9 C1.3,82.5,1.7,83.3,2.4,83.3z"></path><path fill="#3EA8FF" class="st0" d="M61,82.1l22.1-35.5c0.7-1.1-0.1-2.5-1.4-2.5H65.7c-0.6,0-1.2,0.3-1.5,0.8L41.5,81.2c-0.6,0.9,0.1,2.1,1.2,2.1 h16.3C59.8,83.3,60.6,82.9,61,82.1z"></path></svg></a></div></div></header><article class="View_container__VQuzA"><aside class="ContentStickyNavForMobile_container__153a8"><div class="Container_wide__ykGLh Container_common__figYY"><div class="ContentStickyNavForMobile_inner__xJ_fS"><div class="ContentStickyNavForMobile_avatarInner__3_noS"><a class="ContentStickyNavForMobile_principalLink__c15vw" href="/ttskch"><span class="ContentStickyNavForMobile_avatarContainer__V3P_G"><img alt="ttskch" class="AvatarImage_border__pDIjF AvatarImage_plain__Fgp4R " height="38" referrerPolicy="no-referrer" src="https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg" width="38"/></span><span class="ContentStickyNavForMobile_displayName__cmEag">ttskch</span></a></div><div class="ContentStickyNavForMobile_actions__a9fMk"><div style="margin-left:15px"><div class="LikeButton_container__YlckE style-medium count-bottom"><button aria-label="いいね" class="LikeButton_button__ZwdG4"><svg class="LikeButton_svgLike__Gl0Sz" viewBox="0 0 110 110"><path class="LikeButton_svgLikeLine__f9txR" d="M73,24a23.78,23.78,0,0,0-15.89,6.19,3.14,3.14,0,0,1-4.18,0A23.81,23.81,0,0,0,37,24a22,22,0,0,0-22,22c0,16.67,19.64,32.82,25.11,37.93,2.84,2.65,6.15,5.64,8.92,8.13a8.9,8.9,0,0,0,11.9,0c2.77-2.49,6.07-5.48,8.91-8.13C75.37,78.81,95,62.66,95,46A22,22,0,0,0,73,24Z" fill="currentColor"></path><path class="LikeButton_svgLikeInner__uiexS" d="M66.25,76.42c-.71.64-1.32,1.2-1.82,1.67-2.51,2.33-5.39,5-7.94,7.25a2.21,2.21,0,0,1-3,0C51,83,48.1,80.42,45.59,78.09c-.5-.47-1.12-1-1.82-1.67C38.09,71.29,23,57.67,23,46A14,14,0,0,1,37,32a15.92,15.92,0,0,1,11.65,5.23l4.73,5a2.2,2.2,0,0,0,3.23,0l4.72-5A16.06,16.06,0,0,1,73,32,14,14,0,0,1,87,46C87,57.67,71.93,71.29,66.25,76.42Z" fill="currentColor"></path><g class="LikeButton_svgLikeDecoration__78UjB"><circle cx="41.5" cy="9.5" fill="#3ea8ff" r="3.5"></circle><circle cx="98.5" cy="26.5" fill="#ffdc6e" r="3.5"></circle><circle cx="13" cy="19" fill="#c067f4" r="5"></circle><circle cx="77" cy="9" fill="#f76685" r="5"></circle><circle cx="26.5" cy="92.5" fill="#f76685" r="3.5"></circle><circle cx="105.5" cy="48.5" fill="#c067f4" r="3.5"></circle><circle cx="4.5" cy="60.5" fill="#3ea8ff" r="3.5"></circle><circle cx="94.5" cy="73.5" fill="#3ea8ff" r="1.5"></circle><circle cx="16.5" cy="75.5" fill="#ffdc6e" r="1.5"></circle><circle cx="78.5" cy="91.5" fill="#ffdc6e" r="1.5"></circle></g></svg></button></div></div></div></div></div></aside><header class="ArticleHeader_header__IRbtk"><div class="Container_wide__ykGLh Container_common__figYY"><div class="ArticleHeader_main__W98WY"><div class="ArticleHeader_emoji__30JiU"><span class="Emoji_twemoji__hcxYF"><span class="Emoji_twemojiImg__Oc6vR" style="background-image:url(https://asia-northeast1-zenn-dev-production.cloudfunctions.net/twemoji/🐘.svg)"></span></span><span class="Emoji_nativeEmoji__GMBzX">🐘</span></div><h1 class="ArticleHeader_title__9jiOv"><span style="font-size:0.864em">ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました</span></h1><div class="ArticleHeader_metaContainer__5UzrJ"><div class="ArticleHeader_metaInfo__XrRdh"><span class="ArticleHeader_pubDate__gF_sc"><span class="ArticleHeader_num__7Zpz0">2021/06/05</span>に公開</span><div class="ArticleHeader_updateDateContainer__3bpiJ"><div aria-label="更新日" class="ArticleHeader_updateDate__W9Sai" data-tooltip-for-desktop="true" data-tooltip-position="bottom" role="tooltip"><svg x="0px" y="0px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve" height="14" width="14"><g fill="currentColor"><path d="M2.9,10H8c0.2,0,0.4-0.1,0.6-0.3c0.3-0.3,0.3-0.7,0-1.1l-2.2-2c1.5-1.5,3.5-2.3,5.6-2.3c4,0,7.2,2.9,7.7,6.8 c0.1,0.7,0.7,1.1,1.4,1.1l0.1,0c0.8-0.1,1.2-0.8,1.1-1.5c-0.5-5.2-5-9.1-10.3-9.1c-2.8,0-5.5,1.1-7.4,3.1L2.8,2.9 c-0.3-0.3-0.7-0.3-1.1,0C1.5,3.1,1.5,3.3,1.5,3.5v5.1C1.5,9.3,2.1,10,2.9,10z"></path><path d="M21.1,14H16c-0.2,0-0.4,0.1-0.6,0.3c-0.3,0.3-0.3,0.7,0,1.1l2.1,2.1c-1.5,1.5-3.5,2.3-5.6,2.3c-4,0-7.2-2.9-7.7-6.9 c-0.2-0.7-0.9-1.3-1.6-1.2c-0.8,0.1-1.2,0.8-1.1,1.5c0.5,5.2,5,9.1,10.3,9.1c2.8,0,5.5-1.1,7.4-3.1l1.8,1.8c0.3,0.3,0.7,0.3,1.1,0 c0.1-0.1,0.3-0.3,0.3-0.6v-5.1C22.5,14.7,21.9,14,21.1,14z"></path></g></svg><span class="ArticleHeader_num__7Zpz0">2024/05/07</span></div></div></div></div></div></div></header><div class="Container_wide__ykGLh Container_common__figYY"><div class="ContainerUndo_undoInSM__1vdc1"><div class="View_inner__LlCJG"><div class="View_stickyShare__TsaVf"><div class="View_stickyShareInner__FLu2S"><div class="LikeButton_container__YlckE style-large-white count-bottom"><button aria-label="いいね" class="LikeButton_button__ZwdG4"><svg class="LikeButton_svgLike__Gl0Sz" viewBox="0 0 110 110"><path class="LikeButton_svgLikeLine__f9txR" d="M73,24a23.78,23.78,0,0,0-15.89,6.19,3.14,3.14,0,0,1-4.18,0A23.81,23.81,0,0,0,37,24a22,22,0,0,0-22,22c0,16.67,19.64,32.82,25.11,37.93,2.84,2.65,6.15,5.64,8.92,8.13a8.9,8.9,0,0,0,11.9,0c2.77-2.49,6.07-5.48,8.91-8.13C75.37,78.81,95,62.66,95,46A22,22,0,0,0,73,24Z" fill="currentColor"></path><path class="LikeButton_svgLikeInner__uiexS" d="M66.25,76.42c-.71.64-1.32,1.2-1.82,1.67-2.51,2.33-5.39,5-7.94,7.25a2.21,2.21,0,0,1-3,0C51,83,48.1,80.42,45.59,78.09c-.5-.47-1.12-1-1.82-1.67C38.09,71.29,23,57.67,23,46A14,14,0,0,1,37,32a15.92,15.92,0,0,1,11.65,5.23l4.73,5a2.2,2.2,0,0,0,3.23,0l4.72-5A16.06,16.06,0,0,1,73,32,14,14,0,0,1,87,46C87,57.67,71.93,71.29,66.25,76.42Z" fill="currentColor"></path><g class="LikeButton_svgLikeDecoration__78UjB"><circle cx="41.5" cy="9.5" fill="#3ea8ff" r="3.5"></circle><circle cx="98.5" cy="26.5" fill="#ffdc6e" r="3.5"></circle><circle cx="13" cy="19" fill="#c067f4" r="5"></circle><circle cx="77" cy="9" fill="#f76685" r="5"></circle><circle cx="26.5" cy="92.5" fill="#f76685" r="3.5"></circle><circle cx="105.5" cy="48.5" fill="#c067f4" r="3.5"></circle><circle cx="4.5" cy="60.5" fill="#3ea8ff" r="3.5"></circle><circle cx="94.5" cy="73.5" fill="#3ea8ff" r="1.5"></circle><circle cx="16.5" cy="75.5" fill="#ffdc6e" r="1.5"></circle><circle cx="78.5" cy="91.5" fill="#ffdc6e" r="1.5"></circle></g></svg></button></div><span style="display:block;height:1rem;flex-shrink:0"></span><div style="display:flex;gap:0.5rem;flex-direction:column;align-items:stretch;flex-wrap:nowrap"><a aria-label="X(Twitter)にポスト" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgTwitterVertical__NmmfB ShareButtonsExperimental_svgTwitterBase__8FrNq" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://twitter.com/intent/tweet?url=https://zenn.dev/ttskch/articles/1f1572cfd2e375&amp;text=%E3%81%A4%E3%81%84%E3%81%AB%E3%80%81Web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E3%81%AE%E5%B8%B3%E7%A5%A8%E5%8D%B0%E5%88%B7%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%82%92%E7%B7%A8%E3%81%BF%E5%87%BA%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%EF%BD%9Cttskch&amp;hashtags=zenn" id="gtm-article-left-tweet" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="27" height="28" viewBox="0 0 27 28" fill="none"><g clip-path="url(#clip0_1_18)"><path d="M16.0687 11.7356L26.12 0H23.7382L15.0106 10.1899L8.03988 0H0L10.5411 15.4089L0 27.7155H2.38199L11.5985 16.9546L18.9601 27.7155H27L16.0681 11.7356H16.0687ZM12.8062 15.5447L11.7382 14.0103L3.24025 1.80106H6.89884L13.7568 11.6543L14.8248 13.1887L23.7393 25.9963H20.0807L12.8062 15.5452V15.5447Z" fill="currentColor"></path></g><defs><clipPath id="clip0_1_18"><rect width="27" height="27.7297" fill="white"></rect></clipPath></defs></svg></a><a aria-label="Facebookに投稿" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgFacebookVertical__3ykb7 ShareButtonsExperimental_svgFacebookBase___gDOW" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="http://www.facebook.com/sharer.php?u=https://zenn.dev/ttskch/articles/1f1572cfd2e375" id="gtm-article-left-facebook" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="28" height="29" viewBox="0 0 28 29" fill="none"><g clip-path="url(#clip0_2_21)"><path d="M28 14.7812C28 7.02084 21.7319 0.729736 14 0.729736C6.26808 0.729736 0 7.02084 0 14.7812C0 21.3707 4.52032 26.9002 10.6182 28.4189V19.0753H7.73136V14.7812H10.6182V12.9309C10.6182 8.14833 12.7747 5.93158 17.453 5.93158C18.34 5.93158 19.8705 6.10637 20.4966 6.28061V10.1729C20.1662 10.138 19.5922 10.1206 18.8793 10.1206C16.5838 10.1206 15.6968 10.9935 15.6968 13.2625V14.7812H20.2698L19.4841 19.0753H15.6968V28.7297C22.629 27.8895 28.0006 21.9654 28.0006 14.7812H28Z" fill="currentColor"></path></g><defs><clipPath id="clip0_2_21"><rect width="28" height="28" fill="white" transform="translate(0 0.729736)"></rect></clipPath></defs></svg></a><a aria-label="はてなブックマークに登録" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgHatenaBookmarkVertical__b161J ShareButtonsExperimental_svgHatenaBookmarkBase__VXz6p" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://b.hatena.ne.jp/add?mode=confirm&amp;url=https://zenn.dev/ttskch/articles/1f1572cfd2e375&amp;title=%E3%81%A4%E3%81%84%E3%81%AB%E3%80%81Web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E3%81%AE%E5%B8%B3%E7%A5%A8%E5%8D%B0%E5%88%B7%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%82%92%E7%B7%A8%E3%81%BF%E5%87%BA%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%EF%BD%9Cttskch" id="gtm-article-left-hatena-bookmark" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="27" height="28" viewBox="0 0 27 28" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M5.4999 0.729736H21.5001C24.5376 0.729736 27 3.19213 27 6.22964V22.2298C27 25.2673 24.5376 27.7297 21.5001 27.7297H5.4999C2.46239 27.7297 0 25.2673 0 22.2298V6.22964C0 3.19213 2.46239 0.729736 5.4999 0.729736ZM12.98 13.7472C13.8521 13.8136 14.5319 14.1209 15.0212 14.6673V14.6679C15.512 15.2127 15.7572 15.9444 15.7572 16.8543C15.7572 17.5126 15.62 18.0877 15.3436 18.5867C15.0692 19.084 14.6756 19.4712 14.1604 19.7433C13.7441 19.9658 13.2289 20.1278 12.6155 20.2245C12.001 20.3179 10.9966 20.3665 9.6039 20.3665H6.13656V8.09392H9.50184C10.8859 8.09392 11.8503 8.13875 12.4016 8.22623C12.9503 8.31695 13.4206 8.4703 13.8164 8.68792C14.269 8.93902 14.6129 9.27437 14.8527 9.69341C15.0865 10.1151 15.2069 10.6028 15.2069 11.1546C15.2069 11.8512 15.0309 12.4053 14.6761 12.813C14.3197 13.2261 13.756 13.5355 12.98 13.7472ZM9.96192 10.8144H9.24372H9.24318V13.2768H9.91116C10.7352 13.2768 11.2984 13.1856 11.6073 13.0058C11.9124 12.8227 12.0663 12.5273 12.0663 12.0667C12.0663 11.6061 11.9216 11.2821 11.6375 11.0947C11.3497 10.9084 10.7919 10.8144 9.96192 10.8144ZM10.4128 18.1514C11.2028 18.1514 11.7661 18.0531 12.0928 17.8523V17.8528C12.4227 17.653 12.5863 17.3209 12.5863 16.8581C12.5863 16.3408 12.4367 15.9806 12.1338 15.7765C11.8357 15.5724 11.2747 15.4703 10.463 15.4703H9.24372V18.1514H10.4128ZM19.3093 17.257C18.4502 17.257 17.7547 17.9525 17.7547 18.8111C17.7547 19.6697 18.4507 20.3658 19.3093 20.3658C20.1679 20.3658 20.8634 19.6697 20.8634 18.8111C20.8634 17.9525 20.1668 17.257 19.3093 17.257ZM17.9593 8.09318H20.6593V16.2753H17.9593V8.09318Z" fill="currentColor"></path></svg></a></div></div></div><div class="View_columnsContainer__ijfFN"><section class="View_content__OZ_Dc"><div class="View_main__AU6KW"><div class="Container_default__13H8g Container_common__figYY"><div class="View_topics__2sHkl"><a class="View_topicLink__jdtX_" href="/topics/adobexd"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://storage.googleapis.com/zenn-user-upload/topics/9d705b1166.png"/></div><div class="View_topicName____nYp">Adobe XD</div></a><a class="View_topicLink__jdtX_" href="/topics/figma"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://storage.googleapis.com/zenn-user-upload/topics/ea53fb4cd2.png"/></div><div class="View_topicName____nYp">Figma</div></a><a class="View_topicLink__jdtX_" href="/topics/laravel"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://storage.googleapis.com/zenn-user-upload/topics/1303a86ae8.png"/></div><div class="View_topicName____nYp">Laravel</div></a><a class="View_topicLink__jdtX_" href="/topics/php"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://storage.googleapis.com/zenn-user-upload/topics/21186e4563.png"/></div><div class="View_topicName____nYp">PHP</div></a><a class="View_topicLink__jdtX_" href="/topics/%E5%B8%B3%E7%A5%A8"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://zenn.dev/images/topic.png"/></div><div class="View_topicName____nYp">帳票</div></a><a class="View_topicLink__jdtX_" href="/tech-or-idea"><div class="View_topicImage__qMmmw"><img class="View_topicImg__TpkV5" loading="lazy" src="https://static.zenn.studio/images/drawing/tech-icon.svg"/></div><div class="View_topicName____nYp" style="text-transform:capitalize">tech</div></a></div><div class="InsertButtonToCodeBlock_insertButtonWrapper__ueql2"><div class="znc BodyContent_anchorToHeadings__uGxNv"><aside class="msg message"><span class="msg-symbol">!</span><div class="msg-content"> <p>この記事は、2021-06-05に別のブログ媒体に投稿して <a href="https://b.hatena.ne.jp/entry/s/blog.ttskch.com/web-app-pdf-printing-best-practice/" target="_blank" rel="nofollow noopener noreferrer">はてなブックマークで1,000以上ブックマークされた</a> 記事のアーカイブです。</p> </div></aside> <aside class="msg message"><span class="msg-symbol">!</span><div class="msg-content"> <p>この記事で紹介した手順をライブラリ化して公開しました🎉<br> <a href="https://zenn.dev/ttskch/articles/8ee0eaaabf0657" target="_blank">こちらの別記事</a> で使い方など詳しくご紹介していますので、ぜひご参照ください!</p> </div></aside> <h1 id="2024%2F05%2F07-%E8%BF%BD%E8%A8%98"> <a class="header-anchor-link" href="#2024%2F05%2F07-%E8%BF%BD%E8%A8%98" aria-hidden="true"></a> 2024/05/07 追記</h1> <p>最新の登壇スライドバージョンはこちらです。</p> <span class="embed-block embed-speakerdeck"><iframe src="https://speakerdeck.com/player/9ada4bc0ff194c9ab500063c0a75dec5" scrolling="no" allowfullscreen allow="encrypted-media" loading="lazy"></iframe></span> <p>登壇時の様子がYouTubeに上がっているのでよろしければあわせてご覧ください。</p> <p><span class="embed-block embed-youtube"><iframe src="https://www.youtube-nocookie.com/embed/tIxd8C5IDLQ" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe></span><a href="https://www.youtube.com/watch?v=tIxd8C5IDLQ" style="display:none" target="_blank" rel="nofollow noopener noreferrer">https://www.youtube.com/watch?v=tIxd8C5IDLQ</a></p> <h1 id="%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB"> <a class="header-anchor-link" href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB" aria-hidden="true"></a> はじめに</h1> <ul> <li>言い切りタイトルすみません</li> <li>僕を含む一定数の人にとって現時点でのベストプラクティスとなりうる手法という意味で紹介しています</li> <li>極めてシビアな帳票出力の世界にいる人から見ると使い物にならない内容かもしれないと思います</li> <li>帳票印刷の世界では <a href="https://www.wingarc.com/product/svf/lineup/web_pdf.html" target="_blank" rel="nofollow noopener noreferrer">SVF</a> というサービスが有名らしいです。が、こういった外部サービスは使わずに自力で実装するというのがこの記事の前提です</li> <li>動的に明細行の数が増減する連票はこの記事の解説では考慮していませんが、追加で実装するのはそれほど難しくないということは読んでいただければ分かるかなと思います</li> </ul> <h1 id="%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89"> <a class="header-anchor-link" href="#%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89" aria-hidden="true"></a> 結論から</h1> <p><span class="embed-block zenn-embedded zenn-embedded-tweet"><iframe id="zenn-embedded__e2590ee51dfbc" src="https://embed.zenn.studio/tweet#zenn-embedded__e2590ee51dfbc" data-content="https%3A%2F%2Ftwitter.com%2Fttskch%2Fstatus%2F1397926291127508993" frameborder="0" scrolling="no" loading="lazy"></iframe></span><a href="https://twitter.com/ttskch/status/1397926291127508993" style="display:none" target="_blank" rel="nofollow noopener noreferrer">https://twitter.com/ttskch/status/1397926291127508993</a></p> <p>僕が考える現時点でのWebアプリでの帳票印刷のベストプラクティスは、</p> <ul> <li> <a href="https://www.adobe.com/jp/products/xd.html" target="_blank" rel="nofollow noopener noreferrer">Adobe XD</a> や <a href="https://www.figma.com/" target="_blank" rel="nofollow noopener noreferrer">Figma</a> で帳票のレイアウトをデザインして</li> <li>それをSVG形式でエクスポートしたものをテンプレートとしてアプリで読み込み</li> <li>プレースホルダーに当たる文字列を置換した上でSVGをそのままHTMLに埋め込んで出力し</li> <li>SVGの外側のレイアウト(プレビュー画面の見え方、印刷時のページ設定)だけCSSで整え</li> <li>文字の自動縮小・自動折り返し等を別途実装しておき</li> <li>PDF出力やプリンタでの印刷はブラウザの印刷機能を使ってもらう(ブラウザで見えているままが印刷される)</li> </ul> <p>です。</p> <p>色々試しましたが、</p> <ul> <li>ピクセル単位で細かく帳票をデザインできる(しかも簡単に)</li> <li>帳票デザインの保守性が高い(修正が容易)</li> <li>印刷時に見た目が一切崩れない</li> </ul> <p>という条件を満たせる方法は今のところこれしかないという結論です。</p> <p>この方法を使うと、例えばこんな感じの帳票も簡単かつ保守性高く作れます👍👍👍</p> <p><img src="https://user-images.githubusercontent.com/4360663/120876914-9bb2b280-c5ee-11eb-9427-1bd98fa6ba21.png" loading="lazy" class="md-img"></p> <h1 id="%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83"> <a class="header-anchor-link" href="#%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83" aria-hidden="true"></a> デモ環境</h1> <p>下記に実際にアプリを動かせるデモ環境を用意しました。ぜひ触ってみてください。(Herokuの無料プランなので初回起動重いです)</p> <p><a href="https://svg-paper-example.herokuapp.com/" target="_blank" rel="nofollow noopener noreferrer">https://svg-paper-example.herokuapp.com/</a></p> <p>また、このデモのソースコードは以下のリポジトリで公開していますので、あわせてご参照ください。<br> デモはPHP(Laravel)で作ってありますが、知見そのものは他の言語・フレームワークでもそのまま流用できるかと思います。</p> <p><a href="https://github.com/ttskch/svg-paper-example" target="_blank" rel="nofollow noopener noreferrer">https://github.com/ttskch/svg-paper-example</a></p> <h1 id="%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9"> <a class="header-anchor-link" href="#%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9" aria-hidden="true"></a> 既存の方法の欠点</h1> <p>さて、実装方法について説明する前に、既存の方法のどこがダメだったのというのを簡単に話しておきたいと思います。</p> <p>僕の観測している範囲だと、Webアプリでの帳票出力の実装には以下の2つの方法が採用されていることが多そうかなと思っています。</p> <ul> <li>(1) 完全にHTMLで作って、ブラウザの印刷機能で印刷</li> <li>(2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換</li> </ul> <h2 id="(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7"> <a class="header-anchor-link" href="#(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7" aria-hidden="true"></a> (1) 完全にHTMLで作って、ブラウザの印刷機能で印刷</h2> <p>はじめはこの方法ですんなり行けると考えていました。</p> <p>下記のような偉大なる先人の知恵があったので、慣れ親しんだHTML/CSSで帳票をデザインするだけだと。</p> <blockquote> <p><a href="https://qiita.com/cognitom/items/d39d5f19054c8c8fd592" target="_blank" rel="nofollow noopener noreferrer">そろそろ真面目に、HTMLで帳票を描く話をしようか - Qiita</a></p> </blockquote> <blockquote> <p><a href="https://deep-space.blue/web/1858" target="_blank" rel="nofollow noopener noreferrer">【帳票CSS】A4印刷用のHTMLを作ろう(Chrome用) | deep-space.blue</a></p> </blockquote> <p>しかし実際にやってみると、帳票の細かなデザインをHTML/CSSで再現するのがひたすらに面倒臭く、お客さんの要望を細かいところまで再現していった結果、非常に難読なHTML/CSSが出来上がりました😓</p> <p>考えてみれば、帳票のデザインって多くの場合A4一枚にピッタリ収まることが大前提になっていて、Webにおけるページレイアウトのセオリーとはかけ離れているので、保守性を維持しながらこれを作るのは相当難しいです。</p> <p>例えばテーブル(表)1つとっても、普段それほど使わない <code>rowspan</code> <code>colspan</code> を大量に使ってめちゃくちゃ複雑なレイアウトのテーブルを組み立てることとかを普通に要求されます。作るだけならまだしも、その後仕様変更でこのテーブルの中にセルを追加(しかも全体がちゃんとA4に収まるように)しないといけなくなったときのことを考えると、遠い目にならざるを得ません。</p> <p>特に僕のように普段BootstrapなどのCSSフレームワークのレールに乗っかったHTMLしか書いていない人間にはとにかく苦行でしかありませんでした。(普段から複雑なHTMLを書いているデザイナーさんとかにとっては別にしんどくないのかもしれません)</p> <h2 id="(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B"> <a class="header-anchor-link" href="#(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B" aria-hidden="true"></a> (2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換</h2> <p>HTML/CSSのメンテが大変すぎるということが分かったので、思い切ってExcelファイルをテンプレートにする方法を試してみました。</p> <p>帳票を視覚的にデザインできますし、Excelなら(Windows版ならWordも)「縮小して全体を表示」というお馴染みの機能があるのでフォントの縮小についても何も考えなくてよさそうです。</p> <p>調べてみると、<a href="https://ja.libreoffice.org/" target="_blank" rel="nofollow noopener noreferrer">LibreOffice</a> のヘッドレスモードを使えばCLIでExcelファイルのPDFへの変換ができるというこを知り、これなら行けるのではと思いました。</p> <blockquote> <p><a href="https://qiita.com/hirohiro77/items/942eb461e8f4727e4b38" target="_blank" rel="nofollow noopener noreferrer">LibreOfficeでドキュメントコンバータを作ろう - Qiita</a></p> </blockquote> <p>ところがこの方法にも色々と難があり、特に</p> <ul> <li>Excelで帳票の細かいデザインをしようとすると、行の高さ・列の幅を極端に小さくした <strong>地獄のExcel方眼紙</strong> にならざるを得ない</li> <li>ExcelをPDFに変換する際に多少 <strong>見た目が崩れる</strong> </li> <li>同じLibreOfficeでも <strong>Mac版とLinux版でPDFの出力結果が微妙に異なる</strong> </li> </ul> <p>の3点が致命的でした。</p> <p>Excel方眼紙は、セルの大きさがフォント1文字分ぐらいならまだギリ許せる(?)のですが、ピクセル単位に近い微妙なデザインを実現しようと思うと地獄のようにセルを小さくする必要が出てきて心が折れます。</p> <p>LibreOfficeによるPDFへの変換が完璧でない点も、多くの案件において許容不可能でしょう。</p> <h1 id="%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9"> <a class="header-anchor-link" href="#%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9" aria-hidden="true"></a> ベストプラクティスの具体的なやり方</h1> <p>というわけでたどり着いたのが、冒頭でご紹介した方法です。</p> <p>上記2つの方法で満たせなかった</p> <ul> <li>ピクセル単位で細かく帳票をデザインできる(しかも簡単に)</li> <li>帳票デザインの保守性が高い(修正が容易)</li> <li>印刷時に見た目が一切崩れない</li> </ul> <p>という要求を <strong>完璧に満たしてくれる</strong> のがこのSVGを使った方法です👍</p> <p>以下、順を追って具体的なやり方を解説していきます。</p> <h2 id="1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B"> <a class="header-anchor-link" href="#1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B" aria-hidden="true"></a> 1. Adobe XDやFigmaを使って帳票をデザインし、SVG形式でエクスポートする</h2> <p>まず、<a href="https://www.adobe.com/jp/products/xd.html" target="_blank" rel="nofollow noopener noreferrer">Adobe XD</a> や <a href="https://www.figma.com/" target="_blank" rel="nofollow noopener noreferrer">Figma</a> といったUI/UXデザインツールを使って帳票をデザインし、それをSVG形式でエクスポートします。</p> <h3 id="adobe-xd%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86"> <a class="header-anchor-link" href="#adobe-xd%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86" aria-hidden="true"></a> Adobe XDでのSVGエクスポートの手順</h3> <p><code>ファイル &gt; 書き出し &gt; すべてのアートボード</code> でファイル保存のダイアログが出ます。</p> <p><img src="https://res.cloudinary.com/zenn/image/fetch/s--4Fpnf60s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr77qwtl1rj60yk0p4act02.jpg" loading="lazy" class="md-img"></p> <p>ここで <code>フォーマット</code> を <code>SVG</code> にして保存すればOKです。</p> <p>帳票内で画像を使う場合は、下図のように <code>画像を保存</code> の設定を <code>埋め込み</code> にする必要があります。</p> <p><img src="https://res.cloudinary.com/zenn/image/fetch/s--nE2hs89w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr6hhsb9grj31ag0oqjtl.jpg" loading="lazy" class="md-img"></p> <p><code>埋め込み</code> にすると画像はbase64エンコードされてデータURLとして埋め込まれます。</p> <h3 id="figma%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86"> <a class="header-anchor-link" href="#figma%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86" aria-hidden="true"></a> FigmaでのSVGエクスポートの手順</h3> <p>フレーム単位で選択して、右カラム最下部の <code>Export</code> メニューでエクスポートします。</p> <p><img src="https://res.cloudinary.com/zenn/image/fetch/s--CtTF4k8_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr77yr28tjj31nj0u04ow.jpg" loading="lazy" class="md-img"></p> <p>この際、</p> <ul> <li> <code>Include "id" Attribute</code> にチェックを入れる</li> <li> <code>Outline Text</code> の <strong>チェックを外す</strong> </li> </ul> <p>の2点を忘れないようにしてください。</p> <p>後述するJSによる調整の段階で <code>id</code> 属性を使いたいのと、そもそもテキストの置換を行うために文字列を <code>&lt;path&gt;</code> タグではなく <code>&lt;text&gt;</code> タグで出力してほしいのでこの設定が必要です。</p> <blockquote> <p>デモアプリのソースコードの対応するコミットは <a href="https://github.com/ttskch/svg-paper-example/commit/2c0395b8dc1249c6b75caa7638452c211c872778" target="_blank" rel="nofollow noopener noreferrer">こちら</a></p> </blockquote> <h2 id="2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B"> <a class="header-anchor-link" href="#2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B" aria-hidden="true"></a> 2. HTMLにSVGを埋め込み、CSSで印刷に最適化して出力する</h2> <p>SVG形式のテキストファイルができたので、まずはこのテキストをそのままHTMLに埋め込んで画面に出力します。</p> <p>その際、</p> <ul> <li>印刷時にA4縦ぴったりで出力されるように</li> <li>画面表示時に印刷プレビューっぽい見た目で表示されるように</li> </ul> <p>の2点を実現するために多少のCSSを書く必要があります。</p> <p>具体的には以下のような内容でOKです。(これはSCSSで書いてあります)</p> <div class="code-block-container"><pre class="language-scss"><code class="language-scss"><span class="token atrule"><span class="token rule">@page</span></span> <span class="token punctuation">{</span> <span class="token property">size</span><span class="token punctuation">:</span> A4 portrait<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// ヘッダー・フッターが出力されないように</span> <span class="token punctuation">}</span> <span class="token selector">* </span><span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token property">user-select</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">body </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">210</span><span class="token unit">mm</span><span class="token punctuation">;</span> <span class="token property">color-adjust</span><span class="token punctuation">:</span> exact<span class="token punctuation">;</span> <span class="token selector">&gt; svg </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">210</span><span class="token unit">mm</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">295.5</span><span class="token unit">mm</span><span class="token punctuation">;</span> <span class="token comment">// 297mmだと2ページ目にはみ出してしまうので微調整</span> <span class="token property">page-break-after</span><span class="token punctuation">:</span> always<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// プレビュー用</span> <span class="token atrule"><span class="token rule">@media</span> screen</span> <span class="token punctuation">{</span> <span class="token selector">body </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color">#ccc</span><span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span> auto<span class="token punctuation">;</span> <span class="token selector">&gt; svg </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color">#fff</span><span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">.5</span><span class="token unit">mm</span> <span class="token number">2</span><span class="token unit">mm</span> <span class="token color"><span class="token function">rgba</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">.3</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token number">5</span><span class="token unit">mm</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre></div><p>このCSSの意味については今回は詳しい解説は割愛します🙏</p> <p>以下の参考記事を読んでいただければ理解できると思います。</p> <blockquote> <p>参考:</p> <ul> <li><a href="https://qiita.com/cognitom/items/d39d5f19054c8c8fd592" target="_blank" rel="nofollow noopener noreferrer">そろそろ真面目に、HTMLで帳票を描く話をしようか - Qiita</a></li> <li><a href="https://deep-space.blue/web/1858" target="_blank" rel="nofollow noopener noreferrer">【帳票CSS】A4印刷用のHTMLを作ろう(Chrome用) | deep-space.blue</a></li> </ul> </blockquote> <p>この時点で、下図のように <strong>Adobe XDでデザインした帳票がそのままの見た目で印刷プレビューっぽく画面に表示でき、ブラウザの印刷機能を使えばそのままの見た目でPDF出力もできる</strong> という状態まで来ました👍</p> <p><img src="https://user-images.githubusercontent.com/4360663/120877569-4082bf00-c5f2-11eb-8e0e-2ca92d0a7e80.png" loading="lazy" class="md-img"></p> <blockquote> <p>デモアプリのソースコードの対応するコミットは <a href="https://github.com/ttskch/svg-paper-example/commit/da6ec2b41035a42d88cd1b29d0b60f67a5211a5c" target="_blank" rel="nofollow noopener noreferrer">こちら</a></p> </blockquote> <h2 id="3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B"> <a class="header-anchor-link" href="#3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B" aria-hidden="true"></a> 3. 帳票テンプレート内のプレースホルダーを実際の値に置換する</h2> <p>この時点の出力内容は、デザインの時点で埋め込んでおいた <code>%顧客名%</code> のようなプレースホルダー文字列になっているので、出力する前にこれを実際の値に置換する処理を書きます。</p> <p>PHPの場合は、普通に <a href="https://www.php.net/manual/ja/function.str-replace.php" target="_blank" rel="nofollow noopener noreferrer"><code>str_replace()</code></a> で一つひとつ置換していけばOKです。画像を差し替える場合は <code>xlink:href="data:image/png;base64,略"</code> といった画像URL部分を置換します。</p> <p>なお、<code>&lt;text&gt;</code> タグの <code>font-family</code> 属性の値も置換する必要があることに注意しましょう。Adobe XDやFigmaでデザインしたときにテキストオブジェクトに設定していたフォントが <code>font-family</code> 属性に書かれていますが、フォント自体が埋め込まれているわけではないので、別途ロードしたWebフォントに置き換えるか、明朝体とゴシック体の使い分けぐらいでいいなら <code>serif</code> <code>sans-serif</code> に置き換えてユーザーの環境に任せてしまってもよいかと思います。</p> <p>この時点の出力結果は以下のような感じです。内容は実際の値に置換されましたが、テキストが枠をはみ出していますし、金額を右寄せにしたりもしたい感じですね。</p> <p><img src="https://user-images.githubusercontent.com/4360663/120879247-036ffa00-c5fd-11eb-819b-1333f51d3f5d.png" loading="lazy" class="md-img"></p> <blockquote> <p>デモアプリのソースコードの対応するコミットは <a href="https://github.com/ttskch/svg-paper-example/commit/9ed38f15381e2e1efbd74cf9e76a9c1320172133" target="_blank" rel="nofollow noopener noreferrer">こちら</a></p> </blockquote> <h2 id="4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B"> <a class="header-anchor-link" href="#4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B" aria-hidden="true"></a> 4. 一行テキストの自動縮小・中央寄せ・右寄せをJSで処理する</h2> <p>JavaScriptから <code>&lt;text&gt;</code> 要素を( <code>id</code> 属性で指定して)いじることで、文字の自動縮小や配置の調整が可能です👍</p> <p>それぞれ具体的な方法を説明します。</p> <h3 id="%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA"> <a class="header-anchor-link" href="#%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA" aria-hidden="true"></a> 縮小して全体を表示</h3> <p>まずは、Excelにおける <code>縮小して全体を表示</code> 相当の挙動をJavaScriptで実装します。</p> <p>SVGの <code>&lt;text&gt;</code> <code>&lt;tspan&gt;</code> 要素には <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength" target="_blank" rel="nofollow noopener noreferrer"><code>textLength</code></a> という属性があり、テキスト全体の幅を指定することができます。</p> <p><code>textLength</code> をコンテンツ幅よりも小さく設定すると、デフォルトの挙動では文字のサイズは変わらず字間が無理矢理詰められて文字と文字が重なってしまうのですが、<a href="https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/lengthAdjust" target="_blank" rel="nofollow noopener noreferrer"><code>lengthAdjust</code></a> 属性に <code>spacingAndGlyphs</code> を設定することでこの挙動を変更することができます。</p> <p><code>spacingAndGlyphs</code> は、これ以上字間を詰められなくなると文字自体の幅を縮小してくれます。高さは変わらず幅だけが縮小されるので、狭い領域にめちゃくちゃ長いテキストを入れてしまうと異常に縦長な文字になってしまいますが、その状況では仮に縦横比を維持したまま縮小されたとしても字が小さすぎて読めないと思いますし、帳票印刷という文脈ではほぼ気にしなくていいかなと思います。</p> <p>注意すべきは、<code>textLength</code> で指定した幅よりもコンテンツの幅のほうが小さい場合、逆に拡大されてしまうことです。これは、</p> <div class="code-block-container"><pre class="language-js"><code class="language-js"><span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>elem<span class="token punctuation">.</span><span class="token property-access">clientWidth</span> <span class="token operator">&gt;</span> config<span class="token punctuation">.</span><span class="token property-access">textLength</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> elem<span class="token punctuation">.</span><span class="token method function property-access">setAttribute</span><span class="token punctuation">(</span><span class="token string">'textLength'</span><span class="token punctuation">,</span> config<span class="token punctuation">.</span><span class="token property-access">textLength</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre></div><p>といった具合にコンテンツ幅が指定の幅を超えているときのみ <code>textLength</code> を適用するようにすればよいでしょう。</p> <blockquote> <p>一応こんな議論もあるようです。<br> <a href="https://github.com/w3c/svgwg/issues/341" target="_blank" rel="nofollow noopener noreferrer">lengthAdjust values just for shrinking · Issue #341 · w3c/svgwg</a></p> </blockquote> <p>なお、Firefoxでは</p> <ul> <li>インライン要素に対しては <code>clientWidth</code> で幅が取得できない(常に0が返る)という <a href="https://developer.mozilla.org/ja/docs/Web/API/Element/clientWidth" target="_blank" rel="nofollow noopener noreferrer">仕様</a> </li> <li> <code>tspan</code> 要素に対して <code>textLength</code> <code>lengthAdjust</code> 属性が機能しないという <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=890692" target="_blank" rel="nofollow noopener noreferrer">既知のバグ</a> </li> </ul> <p>があるため、追加で <a href="https://github.com/ttskch/svg-paper-example/commit/5c93d73377ee7bd6a6f0197f878075599294b24e" target="_blank" rel="nofollow noopener noreferrer">このような対応</a> が必要になります。</p> <h3 id="%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B"> <a class="header-anchor-link" href="#%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B" aria-hidden="true"></a> 中央寄せ・右寄せ</h3> <p>次に中央寄せ・右寄せについてですが、これは <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor" target="_blank" rel="nofollow noopener noreferrer"><code>text-anchor</code></a> 属性を使うことで実現可能です。</p> <p><code>text-anchor</code> 属性を <code>middle</code> にすれば、基準となるx座標にテキストの中心が来るようになり、<code>end</code> にすれば、基準となるx座標にテキストの末尾が来るようになります。特に指定しなければデフォルトで <code>start</code> という値になり、基準となるx座標にテキストの先頭が来るようになります。</p> <p>「基準となるx座標」とは、<code>text</code> 要素の <a href="https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/transform" target="_blank" rel="nofollow noopener noreferrer"><code>transform</code> 属性</a>(の <code>translate(&lt;x&gt; [&lt;y&gt;])</code> 変換関数 )や <code>tspan</code> 要素の <code>x</code> 属性で指定されているx座標のことです。</p> <p>なので、例えば右寄せを実現したい場合は、</p> <ol> <li> <code>text</code> 要素の <code>transform</code> 属性や <code>tspan</code> 要素の <code>x</code> 属性を操作して、右端となるx座標まで移動させる</li> <li>その上で、<code>text</code> 要素に <code>text-anchor="end"</code> を追加する</li> </ol> <p>という操作が必要になります。</p> <p>具体的な実装例は <a href="https://github.com/ttskch/svg-paper-example/commit/7377c9503152fb8c7c87becfa473db35f09b48d7#diff-e5ed11c1366988d34f3179c4e6d80afaffae57e636aa8617c3527c276ff68b92" target="_blank" rel="nofollow noopener noreferrer">デモアプリの実際のコード</a> をご参照ください。</p> <p>この時点の出力結果は以下のような感じです。(備考とコメント以外の)テキストが枠内に収まり、中央寄せ・右寄せが適切に施されて見た目がだいぶ整いました。</p> <p><img src="https://user-images.githubusercontent.com/4360663/120879192-99575500-c5fc-11eb-971d-6c917f3f19b0.png" loading="lazy" class="md-img"></p> <blockquote> <p>デモアプリのソースコードの対応するコミットは <a href="https://github.com/ttskch/svg-paper-example/commit/7377c9503152fb8c7c87becfa473db35f09b48d7" target="_blank" rel="nofollow noopener noreferrer">こちら</a></p> </blockquote> <h2 id="5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B"> <a class="header-anchor-link" href="#5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B" aria-hidden="true"></a> 5. 複数行テキストの自動折り返し・自動縮小をマークアップの置換で処理する</h2> <p>最後に、複数行テキストの自動折り返し・自動縮小に対応します。これは正直かなりの力技で対応する必要があります。</p> <p>具体的には、</p> <ul> <li> <code>&lt;text&gt;</code> 要素の中に行の数だけ <code>&lt;tspan&gt;</code> 要素を挿入して</li> <li>追加挿入した <code>&lt;tspan&gt;</code> 要素の <code>y</code> 属性を一行分ずつ大きくしていく</li> </ul> <p>という処理を実装します。</p> <p>SVGの <code>&lt;text&gt;</code> 要素には改行の概念がないため、このような力技が必要になります😓</p> <blockquote> <p>SVG 1.1ではこれしかやりようがないのですが、<a href="https://www.w3.org/TR/SVGMobile12/index.html" target="_blank" rel="nofollow noopener noreferrer">SVG Tiny 1.2</a> には <a href="https://www.w3.org/TR/SVGMobile12/text.html#TextAreaElement" target="_blank" rel="nofollow noopener noreferrer"><code>&lt;textArea&gt;</code></a> という要素があり、テキストを自動で折り返してくれるようになっているようです。<br> また、HTMLの <code>&lt;br&gt;</code> に相当する <a href="https://www.w3.org/TR/SVGMobile12/text.html#tbreakElement" target="_blank" rel="nofollow noopener noreferrer"><code>&lt;tbreak&gt;</code></a> という要素もあり、かなり簡単に複数行テキストを扱えそうです。<br> しかし残念ながらGoogle Chromeをはじめ主要なブラウザはSVG Tiny 1.2には対応していません。(要出典🙏)</p> <p>ブラウザがSVG Tiny 1.2に対応しているかどうかは <a href="https://www.w3.org/TR/SVGMobile12/examples/textArea01.svg" target="_blank" rel="nofollow noopener noreferrer">このページ</a> で確認することができます。画面を開いてテキストが表示されれば対応しているということのようです。</p> </blockquote> <p>実装方法は色々考えられますが、僕は</p> <ol> <li>1文字を「一辺が <code>font-size</code> の正方形」と見立ててテキストエリアに収まる縦横の文字数を割り出す(プロポーショナルフォントでは誤差が出るけど無視)</li> <li>横方向にその文字数を超える直前で改行を自動で入れて、もともとテキストが持っていた物理的な改行と合わせて最終的な行数を算出する</li> <li>その行数がテキストエリアの縦文字数よりも大きければ、<code>font-size</code> を少し小さく(0.95倍)して1に戻る、テキストエリアに収まっていれば4へ</li> <li>各行を <code>&lt;tspan&gt;</code> 要素として書き出し、<code>y</code> 属性の値は各行 <code>font-size</code> 分ずつ大きくなるようにする(厳密には、行間も考慮して1.2倍したり)</li> <li>作った文字列で元のSVGのテキストを置換する</li> </ol> <p>という感じの処理を実装しました。実際にはもう少し細かい微調整もしていますが、詳細は <a href="https://github.com/ttskch/svg-paper-example/commit/4bd52550d354f84f608e252f25b7cc5f2f2aae6a#diff-18076594176699aca0198b10dd509feb8cfad7ead6973051b4e28ecb069b5825" target="_blank" rel="nofollow noopener noreferrer">デモアプリの実際のコード</a> をご参照ください🙏</p> <p>ここまでで、無事に完全な帳票が出力できるようになりました🙌</p> <p><img src="https://user-images.githubusercontent.com/4360663/120876914-9bb2b280-c5ee-11eb-9427-1bd98fa6ba21.png" loading="lazy" class="md-img"></p> <p><a href="https://svg-paper-example.herokuapp.com/" target="_blank" rel="nofollow noopener noreferrer">デモアプリ</a> ではリロードする度に出力するテキストの量がランダムに変わるようになっているので、何度かリロードしてみて、どんな内容でも適切に折り返し・縮小されて枠に収まることを確認してみてください😉</p> <blockquote> <p>デモアプリのソースコードの対応するコミットは <a href="https://github.com/ttskch/svg-paper-example/commit/4bd52550d354f84f608e252f25b7cc5f2f2aae6a" target="_blank" rel="nofollow noopener noreferrer">こちら</a></p> </blockquote> <h1 id="%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF"> <a class="header-anchor-link" href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF" aria-hidden="true"></a> ユーザーのブラウザに依存したくない場合は</h1> <p>ユーザーのブラウザの印刷機能に依存したくない場合は、 生成したHTMLのPDFへの変換まで含めてアプリ側でやってしまうとよいかと思います。</p> <p><a href="https://github.com/fraserxu/electron-pdf" target="_blank" rel="nofollow noopener noreferrer">electron-pdf</a> や <a href="https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja#pdf_%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B" target="_blank" rel="nofollow noopener noreferrer">Google Chromeのヘッドレスモード</a> を使えば特に問題なく実現できるでしょう✋</p> <blockquote> <p>ChromeのヘッドレスモードによるPDF出力は、Macなら</p> <div class="code-block-container"><pre class="language-bash"><code class="language-bash">$ /Applications/Google<span class="token punctuation">\</span> Chrome.app/Contents/MacOS/Google<span class="token punctuation">\</span> Chrome <span class="token parameter variable">--headless</span> --disable-gpu --print-to-pdf http://svg-paper-example.herokuapp.com/print/estimate/見積書(金額あり) </code></pre></div> <p>って感じで簡単に試せます。</p> </blockquote> <h1 id="%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB"> <a class="header-anchor-link" href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB" aria-hidden="true"></a> おわりに</h1> <p>というわけで、僕の考えた最強の帳票印刷について解説しました。</p> <p>解説は長くなりましたが、やっていること自体はそんなに複雑ではないですし、一度作ってしまえば他のプロジェクトにも同じ仕組みを流用できます。</p> <p>今のところ自分の中でこれに勝る方法は見つけられていないので、もっといい方法あるよ!という方がいたらぜひ <a href="https://twitter.com/ttskch" target="_blank" rel="nofollow noopener noreferrer">ご一報ください</a> 💪</p> <h1 id="%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8"> <a class="header-anchor-link" href="#%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8" aria-hidden="true"></a> おまけ:HTML/CSSによる帳票デザインを試す中で試行錯誤したこと</h1> <p>おまけというか単なるメモです。試行錯誤の中で分かったことがいくつかあったので書き残しておきます。</p> <details><summary>表示する</summary><div class="details-content"> <h2 id="%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA-%E3%81%AE%E5%AE%9F%E7%8F%BE%E3%81%8C%E6%84%8F%E5%A4%96%E3%81%A8%E5%8E%84%E4%BB%8B"> <a class="header-anchor-link" href="#%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA-%E3%81%AE%E5%AE%9F%E7%8F%BE%E3%81%8C%E6%84%8F%E5%A4%96%E3%81%A8%E5%8E%84%E4%BB%8B" aria-hidden="true"></a> <code>縮小して全体を表示</code> の実現が意外と厄介</h2> <p>Excelにおける <code>縮小して全体を表示</code> 相当の挙動はCSSでは実現不可能なので、JSを使う必要があります。</p> <blockquote> <p><a href="https://css-tricks.com/fitting-text-to-a-container/" target="_blank" rel="nofollow noopener noreferrer">Fitting Text to a Container | CSS-Tricks</a></p> </blockquote> <p>このページなどを参考によさげなライブラリをいくつか(<a href="https://github.com/rikschennink/fitty" target="_blank" rel="nofollow noopener noreferrer">rikschennink/fitty</a>、<a href="https://github.com/STRML/textFit" target="_blank" rel="nofollow noopener noreferrer">STRML/textFit</a> 等)試してみましたが、どうもこの手のライブラリは <strong>フォントサイズをコンテンツ幅いっぱいにフィットさせる</strong> というコンセプトのものばかりで、テキストが多いときには期待どおり縮小されるのですが、<strong>逆にテキストが少ないときに枠いっぱいまで拡大されてしまう</strong> という挙動になってしまいました</p> <p>やりたいのはもちろん縮小のみで拡大は一切されてほしくないのですが、標準の機能でそのような挙動を実現できるライブラリは見つけることができませんでした。</p> <p>なので、複数行テキストのコンテンツにだけ <code>maxSize</code> 的なオプションを使って強引にフォントサイズを固定するようにする必要があります。</p> <p><a href="https://twitter.com/ttskch/status/1395242578191126529" target="_blank" rel="nofollow noopener noreferrer">https://twitter.com/ttskch/status/1395242578191126529</a></p> <h2 id="1%E8%A1%8C%E7%9B%AE%E3%81%8C-colspan-%E3%81%A7%E7%B5%90%E5%90%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A7%E5%90%84%E5%88%97%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95"> <a class="header-anchor-link" href="#1%E8%A1%8C%E7%9B%AE%E3%81%8C-colspan-%E3%81%A7%E7%B5%90%E5%90%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A7%E5%90%84%E5%88%97%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95" aria-hidden="true"></a> 1行目が <code>colspan</code> で結合されているテーブルで各列の幅を固定する方法</h2> <p><a href="https://twitter.com/ttskch/status/1394812266822864901" target="_blank" rel="nofollow noopener noreferrer">https://twitter.com/ttskch/status/1394812266822864901</a></p> <h2 id="flexbox%E3%81%A7%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%82%E3%81%86%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E4%BC%B8%E7%B8%AE%E3%81%95%E3%81%9B%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9"> <a class="header-anchor-link" href="#flexbox%E3%81%A7%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%82%E3%81%86%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E4%BC%B8%E7%B8%AE%E3%81%95%E3%81%9B%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9" aria-hidden="true"></a> flexboxで一方のカラムの幅を固定してもう一方のカラムを伸縮させる場合のベストプラクティス</h2> <p><a href="https://twitter.com/ttskch/status/1394809869543231493" target="_blank" rel="nofollow noopener noreferrer">https://twitter.com/ttskch/status/1394809869543231493</a></p> </div></details> </div></div><span style="display:block;height:1.5rem;flex-shrink:0"></span><a class="Button_secondary__cM38g Button_baseStyle__Vhn6Y Button_small__ErWhv" rel="noopener noreferrer" target="_blank" href="https://github.com/ttskch/zenn-content/blob/main/articles/1f1572cfd2e375.md"><span class="Button_iconLeft__Hqq_M"><svg x="0px" y="0px" viewBox="0 0 27 27" style="enable-background:new 0 0 27 27" xml:space="preserve" height="20" width="20"><path fill="currentColor" d="M13.4,1.2C7,1,1.8,6,1.7,12.4c0,0.1,0,0.4,0,0.5c0,5.1,3.2,9.8,8.2,11.5c0.6,0.1,0.7-0.2,0.7-0.6s0-1.8,0-2.9 c0,0-3.3,0.6-4-1.5c0,0-0.6-1.3-1.3-1.8c0,0-1.1-0.7,0.1-0.7c0.7,0.1,1.5,0.6,1.8,1.2c0.6,1.2,2.2,1.7,3.4,1h0.1 c0.1-0.6,0.4-1.2,0.7-1.6C8.7,17.1,6,16.9,6,12.3c0-1.1,0.5-2.1,1.2-2.8c0-1.1,0-2.2,0.3-3.2c1-0.4,3.3,1.3,3.3,1.3c2-0.6,4-0.6,6,0 c0,0,2.2-1.6,3.2-1.2c0.5,1,0.5,2.2,0.1,3.2c0.7,0.7,1.2,1.8,1.2,2.8c0,4.5-2.8,5-5.5,5.2c0.6,0.6,0.9,1.3,0.7,2.2c0,1.7,0,3.5,0,4 s0.2,0.6,0.7,0.6c4.9-1.7,8.2-6.2,8-11.5c0.1-6.4-5.1-11.6-11.6-11.6C13.5,1.2,13.4,1.2,13.4,1.2z"></path></svg></span>GitHubで編集を提案</a><div class="View_actions__s_UJk" id="share"><div class="LikeButton_container__YlckE style-large"><button aria-label="いいね" class="LikeButton_button__ZwdG4"><svg class="LikeButton_svgLike__Gl0Sz" viewBox="0 0 110 110"><path class="LikeButton_svgLikeLine__f9txR" d="M73,24a23.78,23.78,0,0,0-15.89,6.19,3.14,3.14,0,0,1-4.18,0A23.81,23.81,0,0,0,37,24a22,22,0,0,0-22,22c0,16.67,19.64,32.82,25.11,37.93,2.84,2.65,6.15,5.64,8.92,8.13a8.9,8.9,0,0,0,11.9,0c2.77-2.49,6.07-5.48,8.91-8.13C75.37,78.81,95,62.66,95,46A22,22,0,0,0,73,24Z" fill="currentColor"></path><path class="LikeButton_svgLikeInner__uiexS" d="M66.25,76.42c-.71.64-1.32,1.2-1.82,1.67-2.51,2.33-5.39,5-7.94,7.25a2.21,2.21,0,0,1-3,0C51,83,48.1,80.42,45.59,78.09c-.5-.47-1.12-1-1.82-1.67C38.09,71.29,23,57.67,23,46A14,14,0,0,1,37,32a15.92,15.92,0,0,1,11.65,5.23l4.73,5a2.2,2.2,0,0,0,3.23,0l4.72-5A16.06,16.06,0,0,1,73,32,14,14,0,0,1,87,46C87,57.67,71.93,71.29,66.25,76.42Z" fill="currentColor"></path><g class="LikeButton_svgLikeDecoration__78UjB"><circle cx="41.5" cy="9.5" fill="#3ea8ff" r="3.5"></circle><circle cx="98.5" cy="26.5" fill="#ffdc6e" r="3.5"></circle><circle cx="13" cy="19" fill="#c067f4" r="5"></circle><circle cx="77" cy="9" fill="#f76685" r="5"></circle><circle cx="26.5" cy="92.5" fill="#f76685" r="3.5"></circle><circle cx="105.5" cy="48.5" fill="#c067f4" r="3.5"></circle><circle cx="4.5" cy="60.5" fill="#3ea8ff" r="3.5"></circle><circle cx="94.5" cy="73.5" fill="#3ea8ff" r="1.5"></circle><circle cx="16.5" cy="75.5" fill="#ffdc6e" r="1.5"></circle><circle cx="78.5" cy="91.5" fill="#ffdc6e" r="1.5"></circle></g></svg></button></div><div class="View_menu__wgMxq"><button aria-label="その他の操作" class="PopoverMenuButton_menuButton__hKCa_"><svg viewBox="0 0 27 27" height="16" width="16"><path fill="currentColor" d="M12.74,20.53,3.48,11.35a.75.75,0,0,1,0-1.07L4.71,9.05a.75.75,0,0,1,1.07,0l7.49,7.41,7.49-7.41a.74.74,0,0,1,1.06,0l1.24,1.23a.77.77,0,0,1,0,1.07L13.8,20.53A.74.74,0,0,1,12.74,20.53Z"></path></svg></button></div><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a aria-label="X(Twitter)にポスト" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgTwitterHorizontal__5xDGM ShareButtonsExperimental_svgTwitterBase__8FrNq" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://twitter.com/intent/tweet?url=https://zenn.dev/ttskch/articles/1f1572cfd2e375&amp;text=%E3%81%A4%E3%81%84%E3%81%AB%E3%80%81Web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E3%81%AE%E5%B8%B3%E7%A5%A8%E5%8D%B0%E5%88%B7%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%82%92%E7%B7%A8%E3%81%BF%E5%87%BA%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%EF%BD%9Cttskch&amp;hashtags=zenn" id="gtm-article-footer-tweet" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="27" height="28" viewBox="0 0 27 28" fill="none"><g clip-path="url(#clip0_1_18)"><path d="M16.0687 11.7356L26.12 0H23.7382L15.0106 10.1899L8.03988 0H0L10.5411 15.4089L0 27.7155H2.38199L11.5985 16.9546L18.9601 27.7155H27L16.0681 11.7356H16.0687ZM12.8062 15.5447L11.7382 14.0103L3.24025 1.80106H6.89884L13.7568 11.6543L14.8248 13.1887L23.7393 25.9963H20.0807L12.8062 15.5452V15.5447Z" fill="currentColor"></path></g><defs><clipPath id="clip0_1_18"><rect width="27" height="27.7297" fill="white"></rect></clipPath></defs></svg></a><a aria-label="Facebookに投稿" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgFacebookHorizontal__MpfBm ShareButtonsExperimental_svgFacebookBase___gDOW" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="http://www.facebook.com/sharer.php?u=https://zenn.dev/ttskch/articles/1f1572cfd2e375" id="gtm-article-footer-facebook" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="28" height="29" viewBox="0 0 28 29" fill="none"><g clip-path="url(#clip0_2_21)"><path d="M28 14.7812C28 7.02084 21.7319 0.729736 14 0.729736C6.26808 0.729736 0 7.02084 0 14.7812C0 21.3707 4.52032 26.9002 10.6182 28.4189V19.0753H7.73136V14.7812H10.6182V12.9309C10.6182 8.14833 12.7747 5.93158 17.453 5.93158C18.34 5.93158 19.8705 6.10637 20.4966 6.28061V10.1729C20.1662 10.138 19.5922 10.1206 18.8793 10.1206C16.5838 10.1206 15.6968 10.9935 15.6968 13.2625V14.7812H20.2698L19.4841 19.0753H15.6968V28.7297C22.629 27.8895 28.0006 21.9654 28.0006 14.7812H28Z" fill="currentColor"></path></g><defs><clipPath id="clip0_2_21"><rect width="28" height="28" fill="white" transform="translate(0 0.729736)"></rect></clipPath></defs></svg></a><a aria-label="はてなブックマークに登録" class="ShareButtonsExperimental_button__d9aXF ShareButtonsExperimental_svgHatenaBookmarkHorizontal__ccpWn ShareButtonsExperimental_svgHatenaBookmarkBase__VXz6p" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://b.hatena.ne.jp/add?mode=confirm&amp;url=https://zenn.dev/ttskch/articles/1f1572cfd2e375&amp;title=%E3%81%A4%E3%81%84%E3%81%AB%E3%80%81Web%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A7%E3%81%AE%E5%B8%B3%E7%A5%A8%E5%8D%B0%E5%88%B7%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%82%92%E7%B7%A8%E3%81%BF%E5%87%BA%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%EF%BD%9Cttskch" id="gtm-article-footer-hatena-bookmark" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="27" height="28" viewBox="0 0 27 28" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M5.4999 0.729736H21.5001C24.5376 0.729736 27 3.19213 27 6.22964V22.2298C27 25.2673 24.5376 27.7297 21.5001 27.7297H5.4999C2.46239 27.7297 0 25.2673 0 22.2298V6.22964C0 3.19213 2.46239 0.729736 5.4999 0.729736ZM12.98 13.7472C13.8521 13.8136 14.5319 14.1209 15.0212 14.6673V14.6679C15.512 15.2127 15.7572 15.9444 15.7572 16.8543C15.7572 17.5126 15.62 18.0877 15.3436 18.5867C15.0692 19.084 14.6756 19.4712 14.1604 19.7433C13.7441 19.9658 13.2289 20.1278 12.6155 20.2245C12.001 20.3179 10.9966 20.3665 9.6039 20.3665H6.13656V8.09392H9.50184C10.8859 8.09392 11.8503 8.13875 12.4016 8.22623C12.9503 8.31695 13.4206 8.4703 13.8164 8.68792C14.269 8.93902 14.6129 9.27437 14.8527 9.69341C15.0865 10.1151 15.2069 10.6028 15.2069 11.1546C15.2069 11.8512 15.0309 12.4053 14.6761 12.813C14.3197 13.2261 13.756 13.5355 12.98 13.7472ZM9.96192 10.8144H9.24372H9.24318V13.2768H9.91116C10.7352 13.2768 11.2984 13.1856 11.6073 13.0058C11.9124 12.8227 12.0663 12.5273 12.0663 12.0667C12.0663 11.6061 11.9216 11.2821 11.6375 11.0947C11.3497 10.9084 10.7919 10.8144 9.96192 10.8144ZM10.4128 18.1514C11.2028 18.1514 11.7661 18.0531 12.0928 17.8523V17.8528C12.4227 17.653 12.5863 17.3209 12.5863 16.8581C12.5863 16.3408 12.4367 15.9806 12.1338 15.7765C11.8357 15.5724 11.2747 15.4703 10.463 15.4703H9.24372V18.1514H10.4128ZM19.3093 17.257C18.4502 17.257 17.7547 17.9525 17.7547 18.8111C17.7547 19.6697 18.4507 20.3658 19.3093 20.3658C20.1679 20.3658 20.8634 19.6697 20.8634 18.8111C20.8634 17.9525 20.1668 17.257 19.3093 17.257ZM17.9593 8.09318H20.6593V16.2753H17.9593V8.09318Z" fill="currentColor"></path></svg></a></div></div><span style="display:block;height:2rem;flex-shrink:0"></span><aside class="View_authorInfo__F19rR"><div class="ProfileCard_container__YfvQl"><a class="ProfileCard_avatar__tIJpR" href="/ttskch"><img alt="ttskch" class="AvatarImage_plain__Fgp4R " height="80" loading="lazy" referrerPolicy="no-referrer" src="https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg" width="80"/></a><div class="ProfileCard_name__qXamf"><a class="ProfileCard_displayName__gRUeY" href="/ttskch">ttskch</a></div><div class="ProfileCard_content__1w905"><p class="Paragraph_common__yRSrj Paragraph_sidenote__9NTjJ Paragraph_decorateLink__aIAFh"><span>Kannade Inc. CEO←フリーランス←カルテットコミュニケーションズ創業CTO。社外CTO/技術顧問数社。PHPとTypeScript。Symfonyの知見をよくブログに書きます。お仕事のご相談いつでもTwitter DMまで✉️詳細はリンク先にて</span></p><div class="ProfileCard_actions__2ZjZ8"><span class="ProfileCard_follow__ng60N"></span><a aria-label="@ttskch" class="ProfileCard_link__oexkj ProfileCard_linkBase__hVELe" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://github.com/ttskch" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg x="0px" y="0px" viewBox="0 0 27 27" style="enable-background:new 0 0 27 27" xml:space="preserve" aria-label="GitHub"><path fill="currentColor" d="M13.4,1.2C7,1,1.8,6,1.7,12.4c0,0.1,0,0.4,0,0.5c0,5.1,3.2,9.8,8.2,11.5c0.6,0.1,0.7-0.2,0.7-0.6s0-1.8,0-2.9 c0,0-3.3,0.6-4-1.5c0,0-0.6-1.3-1.3-1.8c0,0-1.1-0.7,0.1-0.7c0.7,0.1,1.5,0.6,1.8,1.2c0.6,1.2,2.2,1.7,3.4,1h0.1 c0.1-0.6,0.4-1.2,0.7-1.6C8.7,17.1,6,16.9,6,12.3c0-1.1,0.5-2.1,1.2-2.8c0-1.1,0-2.2,0.3-3.2c1-0.4,3.3,1.3,3.3,1.3c2-0.6,4-0.6,6,0 c0,0,2.2-1.6,3.2-1.2c0.5,1,0.5,2.2,0.1,3.2c0.7,0.7,1.2,1.8,1.2,2.8c0,4.5-2.8,5-5.5,5.2c0.6,0.6,0.9,1.3,0.7,2.2c0,1.7,0,3.5,0,4 s0.2,0.6,0.7,0.6c4.9-1.7,8.2-6.2,8-11.5c0.1-6.4-5.1-11.6-11.6-11.6C13.5,1.2,13.4,1.2,13.4,1.2z"></path></svg></a><a aria-label="@ttskch" class="ProfileCard_twitterLink__l4sOK ProfileCard_linkBase__hVELe" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://twitter.com/ttskch" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg width="27" height="28" viewBox="0 0 27 28" fill="none" aria-label="X(Twitter)"><g clip-path="url(#clip0_1_18)"><path d="M16.0687 11.7356L26.12 0H23.7382L15.0106 10.1899L8.03988 0H0L10.5411 15.4089L0 27.7155H2.38199L11.5985 16.9546L18.9601 27.7155H27L16.0681 11.7356H16.0687ZM12.8062 15.5447L11.7382 14.0103L3.24025 1.80106H6.89884L13.7568 11.6543L14.8248 13.1887L23.7393 25.9963H20.0807L12.8062 15.5452V15.5447Z" fill="currentColor"></path></g><defs><clipPath id="clip0_1_18"><rect width="27" height="27.7297" fill="white"></rect></clipPath></defs></svg></a><a aria-label="ttskch.com" class="ProfileCard_link__oexkj ProfileCard_linkBase__hVELe" data-tooltip-for-desktop="true" data-tooltip-position="bottom" href="https://ttskch.com" rel="nofollow noopener noreferrer" role="tooltip" target="_blank"><svg x="0px" y="0px" viewBox="0 0 27 27" style="enable-background:new 0 0 27 27" xml:space="preserve" aria-label="リンク"><path fill="currentColor" d="M9.6,23.9c-3.6,0-6.5-3-6.5-6.6c0-1.7,0.7-3.4,1.9-4.6l2.3-2.3c0.5-0.4,1.2-0.4,1.6,0.1c0.4,0.4,0.4,1,0,1.5l-2.3,2.3 c-1.7,1.7-1.7,4.4,0,6.1s4.4,1.7,6.1,0l2.3-2.3c0.5-0.4,1.2-0.4,1.6,0.1c0.4,0.4,0.4,1,0,1.5L14.3,22C13,23.2,11.4,23.9,9.6,23.9z M10.6,17.5c-0.6,0-1.1-0.5-1.1-1.1c0-0.3,0.1-0.6,0.3-0.8l5.8-5.8c0.4-0.4,1.1-0.4,1.6,0c0.4,0.4,0.4,1.1,0,1.6l-5.8,5.8 C11.2,17.4,10.9,17.5,10.6,17.5z M18.9,16.9c-0.3,0-0.6-0.1-0.8-0.3c-0.4-0.4-0.4-1.1,0-1.6l2.3-2.3c1.7-1.7,1.7-4.4,0-6.1 c-1.7-1.7-4.4-1.7-6.1,0L12,8.9c-0.5,0.4-1.2,0.4-1.6-0.1c-0.4-0.4-0.4-1,0-1.5L12.7,5c2.6-2.6,6.7-2.6,9.2,0s2.6,6.7,0,9.2 l-2.3,2.4C19.4,16.8,19.1,16.9,18.9,16.9z"></path></svg></a></div></div></div></aside><span style="display:block;height:2rem;flex-shrink:0"></span><aside class="SendBadgeCta_container__peDjp"><div class="SendBadgeCta_content__FGxIx"><div class="SendBadgeCta_title__NFpA3">バッジを贈って著者を応援しよう</div><span style="display:block;height:0.5rem;flex-shrink:0"></span><p class="Paragraph_common__yRSrj Paragraph_sidenote-sm__zfJpo">バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。</p><div class="SendBadgeCta_buttonContainer__5_7_u"><button class="Button_primary__VcoA9 Button_baseStyle__Vhn6Y Button_large__jO0IW Button_fontBold__BN6Co">バッジを贈る</button></div></div><svg viewBox="0 0 318 451" fill="currentColor" class="SendBadgeCta_icon__mcUsF"><path d="M175.12,41.58V13.2a8.87,8.87,0,0,0-17.73.6V41.58a8.87,8.87,0,1,0,17.73.6Z"></path><path d="M75,44.52,94.62,64.17c.1.11.21.22.32.32a8.87,8.87,0,0,0,12.18-12.86L87.52,32A8.87,8.87,0,0,0,75,44.52Z"></path><path d="M253.56,44.52A8.87,8.87,0,0,0,241,32L221.37,51.63,221,52A8.87,8.87,0,0,0,233.9,64.17h0Z"></path><path d="M18.06,262.07a28.08,28.08,0,0,0-.7,39.08l22.55,27.78,12.58,15.5,32.78,40.4A24.72,24.72,0,0,0,104.53,394H203a35.83,35.83,0,0,1,15.13,3.36,8,8,0,0,0,9-1.68h0a8.09,8.09,0,0,0-2.2-13h0a46,46,0,0,0-19.6-4.4H104.57a9.06,9.06,0,0,1-7.08-3.37L72.76,344.4,58.37,326.68l-.72-.89L39.91,303.93,29.32,290.42h0a12.35,12.35,0,0,1,0-17.46,10.33,10.33,0,0,1,1.43-1.2,12.1,12.1,0,0,1,4.76-2.15h.17a12.73,12.73,0,0,1,2.06-.21H38a12.47,12.47,0,0,1,3.53.52l.71.23c.23.08.45.18.68.27h0A12.55,12.55,0,0,1,46.76,273l11,10.68,14,13.53,21.88,21.13a23.65,23.65,0,0,0-.56,2.63,8.77,8.77,0,0,0-.21,1.6l-.06.84v3.51a3.31,3.31,0,0,1,0,.62,2.53,2.53,0,0,0,0,.55,1.79,1.79,0,0,0,.07.62c.07.18,0,.33.07.54s.08.42.1.62l.09.54.12.61.12.53a2.93,2.93,0,0,0,.15.6c0,.18.09.33.14.53s.12.43.17.6l.16.52c.06.19.13.42.19.59s.12.34.19.51l.21.58.21.51.24.57c.07.17.15.31.23.5s.17.37.26.55l.25.51.28.53.28.5.3.52.3.49c.11.16.21.33.32.5l.33.48.33.49c.12.16.24.31.35.47l.36.47.37.47c.13.14.24.28.37.44s.33.37.41.46.15.18.23.26l.15.17.43.45.4.4.46.44.4.38.5.43.41.36.53.42.42.33.56.41.42.31.61.4.41.27.66.4.4.24.72.39.38.2.8.4.33.16.94.4.23.1c.79.33,1.61.62,2.45.88a26.76,26.76,0,0,0,5.83,1.08,21.47,21.47,0,0,0,2.78.11H184.7l.48-.07.28-.06.43-.11.28-.09.42-.15.25-.11a2.93,2.93,0,0,0,.44-.21l.21-.1c.2-.11.4-.23.59-.36l.17-.12.38-.29c.2-.16.4-.34.59-.52h0c.19-.19.38-.4.56-.61l.11-.15c.13-.17.25-.35.37-.52l.15-.23.27-.49.13-.26.21-.49a.45.45,0,0,1,.1-.3,1.7,1.7,0,0,0,.16-.51,1.06,1.06,0,0,1,.07-.3c.05-.2.08-.39.11-.58v-.26a6.31,6.31,0,0,0,0-.87,10.7,10.7,0,0,0-.11-1.27,7.87,7.87,0,0,0-7.76-6.6H120.77a12.35,12.35,0,0,1-12.3-13.57,5.65,5.65,0,0,1,.16-1.07v-.23c.07-.33.15-.67.25-1h0c.11-.36.23-.68.36-1a.94.94,0,0,1,.1-.23c.12-.29.26-.58.41-.86v-.07c.16-.3.34-.58.52-.86l.16-.24.15-.22c.12-.17.25-.33.37-.48l.08-.11c.21-.25.43-.5.66-.74l.19-.19c.22-.22.44-.43.68-.63l.06-.06c.26-.22.52-.42.8-.62l.21-.15c.26-.18.53-.36.8-.52h.06c.29-.17.59-.32.9-.47l.24-.12c.29-.13.59-.26.89-.37h.07c.35-.12.67-.22,1-.31l.27-.08c.36-.08.67-.16,1-.22h.07a4.73,4.73,0,0,1,1-.14h65.95a46.51,46.51,0,0,0,12.79-1.77,43.8,43.8,0,0,0,4.85-1.75h.09c.76-.34,1.51-.69,2.25-1.07a62.92,62.92,0,0,1,29.3-7.17h3.1l1.45.08.93.08a11.73,11.73,0,0,1,1.33.12l1,.11.94.11.38.06.87.13,1.32.21.91.17,1.28.25.91.2,1.26.3.91.22,1.24.34.9.25,1.28.39.83.27c.59.19,1.17.39,1.75.6l1.25.47c.31.12.63.23.94.36H260c.89.35,1.75.72,2.62,1.1l.44.21c.71.32,1.4.66,2.09,1l.68.32c.67.36,1.34.71,2,1.08l.47.26c.81.41,1.62.92,2.41,1.41l.42.27c.64.39,1.28.81,1.91,1.23l.67.46c.58.4,1.16.81,1.73,1.23l.54.39c.73.55,1.46,1.11,2.16,1.69l.38.31c.58.47,1.15,1,1.72,1.47l.63.56c.48.46,1,.92,1.48,1.38l.55.53c.23.24,1.5,1.75,3,3.55a5.2,5.2,0,0,0,.51.55,8,8,0,0,0,11.31,0h0a8,8,0,0,0,.36-11q-1.08-1.23-2.22-2.43c-1.68-1.73-3.43-3.39-5.25-5V207.16h7a8.88,8.88,0,0,0,8.87-8.86V152.38a8.88,8.88,0,0,0-8.87-8.87H284.85c5.62-15,1.68-32.58-10.89-43.16a49.14,49.14,0,0,0-28.23-11.56c-20.31-1.46-44.09,12.21-58.31,24.38-1,.89-2.13,1.82-3.13,2.76h-38c-1-.94-2.14-1.87-3.14-2.76-14.22-12.16-38-25.84-58.33-24.38a49,49,0,0,0-28.24,11.56C44,110.93,40.12,128.51,45.69,143.51H32.91a8.88,8.88,0,0,0-8.86,8.87V198.3a8.85,8.85,0,0,0,8.86,8.86h7v46.77a20.38,20.38,0,0,0-2.06-.08A27.76,27.76,0,0,0,18.06,262ZM196,207.17h76.88v87.5c-1.46-.72-2.92-1.42-4.39-2.08a83.5,83.5,0,0,0-14.76-4.9,81.38,81.38,0,0,0-18.58-2.15,78.77,78.77,0,0,0-36.42,8.92,25.37,25.37,0,0,1-2.68,1.18Zm.05-45.91h92.74v28.17h-92.7Zm0-32c.9-.86,1.9-1.71,2.9-2.57,12-10.28,30.55-20.22,43.76-20.22.6,0,1.17.06,1.75.06a31.32,31.32,0,0,1,18.08,7.4c8.65,7.25,9.65,20.71,2.08,29.58H196.06Zm-43.71,4.42h26V297.39h-26Zm-17.78,9.86H66C58.4,134.68,59.41,121.22,68,114a31.34,31.34,0,0,1,18.09-7.47c13.28-1,33,9.41,45.5,20.16,1,.86,2,1.72,2.92,2.57ZM57.65,207.16h76.88v90.2H121.27A29,29,0,0,0,102,304.8l-19.1-18.45L57.69,262Zm-15.86-45.9h92.74v28.17H41.83Z"></path></svg></aside></div></div><div id="discuss"><div class="ArticleComments_commentsContainer__kOO0n"><section class="ArticleComments_comments__y4Azs"><div class="ArticleComments_emptyContainer__I4fw6"><h3 class="Heading_size-lg__KD3Up Heading_centered__lNLF_">Discussion</h3><img class="ArticleComments_emptyImg__FGwCr" src="https://static.zenn.studio/images/drawing/discussion.png" width="300"/></div></section></div></div></section><aside class="View_sidebarContainer__YwcNH"><div class="ArticleSidebar_container__jSRJw"><div><div class="ArticleSidebar_user__vJ7nz ArticleSidebar_sidebarCard__AtM_Z"><div class="SidebarUserBio_container__iWemi"><a href="/ttskch"><img alt="ttskch" class="AvatarImage_border__pDIjF AvatarImage_plain__Fgp4R " height="60" loading="lazy" referrerPolicy="no-referrer" src="https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg" width="60"/></a><div class="SidebarUserBio_author__cM7pP"><a class="SidebarUserBio_name__0zFdT" href="/ttskch">ttskch</a><div class="SidebarUserBio_actions__oFupD"><a class="SidebarUserBio_link__nnh24" href="https://github.com/ttskch" rel="nofollow noopener noreferrer" target="_blank"><span aria-label="@ttskch" data-tooltip-for-desktop="true" data-tooltip-position="bottom" role="tooltip"><svg x="0px" y="0px" viewBox="0 0 27 27" style="enable-background:new 0 0 27 27" xml:space="preserve" aria-label="GitHub"><path fill="currentColor" d="M13.4,1.2C7,1,1.8,6,1.7,12.4c0,0.1,0,0.4,0,0.5c0,5.1,3.2,9.8,8.2,11.5c0.6,0.1,0.7-0.2,0.7-0.6s0-1.8,0-2.9 c0,0-3.3,0.6-4-1.5c0,0-0.6-1.3-1.3-1.8c0,0-1.1-0.7,0.1-0.7c0.7,0.1,1.5,0.6,1.8,1.2c0.6,1.2,2.2,1.7,3.4,1h0.1 c0.1-0.6,0.4-1.2,0.7-1.6C8.7,17.1,6,16.9,6,12.3c0-1.1,0.5-2.1,1.2-2.8c0-1.1,0-2.2,0.3-3.2c1-0.4,3.3,1.3,3.3,1.3c2-0.6,4-0.6,6,0 c0,0,2.2-1.6,3.2-1.2c0.5,1,0.5,2.2,0.1,3.2c0.7,0.7,1.2,1.8,1.2,2.8c0,4.5-2.8,5-5.5,5.2c0.6,0.6,0.9,1.3,0.7,2.2c0,1.7,0,3.5,0,4 s0.2,0.6,0.7,0.6c4.9-1.7,8.2-6.2,8-11.5c0.1-6.4-5.1-11.6-11.6-11.6C13.5,1.2,13.4,1.2,13.4,1.2z"></path></svg></span></a><a class="SidebarUserBio_twitterLink__yGgDq SidebarUserBio_link__nnh24" href="https://twitter.com/ttskch" rel="nofollow noopener noreferrer" target="_blank"><span aria-label="@ttskch" data-tooltip-for-desktop="true" data-tooltip-position="bottom" role="tooltip"><svg width="27" height="28" viewBox="0 0 27 28" fill="none" aria-label="X(Twitter)"><g clip-path="url(#clip0_1_18)"><path d="M16.0687 11.7356L26.12 0H23.7382L15.0106 10.1899L8.03988 0H0L10.5411 15.4089L0 27.7155H2.38199L11.5985 16.9546L18.9601 27.7155H27L16.0681 11.7356H16.0687ZM12.8062 15.5447L11.7382 14.0103L3.24025 1.80106H6.89884L13.7568 11.6543L14.8248 13.1887L23.7393 25.9963H20.0807L12.8062 15.5452V15.5447Z" fill="currentColor"></path></g><defs><clipPath id="clip0_1_18"><rect width="27" height="27.7297" fill="white"></rect></clipPath></defs></svg></span></a><a class="SidebarUserBio_link__nnh24" href="https://ttskch.com" rel="nofollow noopener noreferrer" style="margin-left:-2px" target="_blank"><span aria-label="ttskch.com" data-tooltip-for-desktop="true" data-tooltip-position="bottom" role="tooltip"><svg x="0px" y="0px" viewBox="0 0 27 27" style="enable-background:new 0 0 27 27" xml:space="preserve" aria-label="リンク"><path fill="currentColor" d="M9.6,23.9c-3.6,0-6.5-3-6.5-6.6c0-1.7,0.7-3.4,1.9-4.6l2.3-2.3c0.5-0.4,1.2-0.4,1.6,0.1c0.4,0.4,0.4,1,0,1.5l-2.3,2.3 c-1.7,1.7-1.7,4.4,0,6.1s4.4,1.7,6.1,0l2.3-2.3c0.5-0.4,1.2-0.4,1.6,0.1c0.4,0.4,0.4,1,0,1.5L14.3,22C13,23.2,11.4,23.9,9.6,23.9z M10.6,17.5c-0.6,0-1.1-0.5-1.1-1.1c0-0.3,0.1-0.6,0.3-0.8l5.8-5.8c0.4-0.4,1.1-0.4,1.6,0c0.4,0.4,0.4,1.1,0,1.6l-5.8,5.8 C11.2,17.4,10.9,17.5,10.6,17.5z M18.9,16.9c-0.3,0-0.6-0.1-0.8-0.3c-0.4-0.4-0.4-1.1,0-1.6l2.3-2.3c1.7-1.7,1.7-4.4,0-6.1 c-1.7-1.7-4.4-1.7-6.1,0L12,8.9c-0.5,0.4-1.2,0.4-1.6-0.1c-0.4-0.4-0.4-1,0-1.5L12.7,5c2.6-2.6,6.7-2.6,9.2,0s2.6,6.7,0,9.2 l-2.3,2.4C19.4,16.8,19.1,16.9,18.9,16.9z"></path></svg></span></a></div></div></div><span style="display:block;height:1rem;flex-shrink:0"></span><p class="Paragraph_common__yRSrj Paragraph_description-sm__vmr99 Paragraph_decorateLink__aIAFh"><span>Kannade Inc. CEO←フリーランス←カルテットコミュニケーションズ創業CTO。社外CTO/技術顧問数社。PHPとTypeScript。Symfonyの知見をよくブログに書きます。お仕事のご相談いつでもTwitter DMまで✉️詳細はリンク先にて</span></p><span style="display:block;height:1rem;flex-shrink:0"></span><button class="Button_primary__VcoA9 Button_baseStyle__Vhn6Y Button_full__RIG_z Button_large__jO0IW Button_fontBold__BN6Co">バッジを贈る</button><div class="ArticleSidebar_badgeAbout__Y3cIb"><a class="TextAnchor_anchorColoredSubtle__ZUESd TextAnchor_anchorColored__PlNnP ArticleSidebar_badgeAboutLink__aqdNT" href="/faq#badges">バッジを贈るとは<svg viewBox="0 0 24 24" fill="currentColor" class="TextAnchor_icon__1hdMx"><path d="M0 0h24v24H0z" fill="none"></path><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"></path></svg></a></div></div></div><div class="ArticleSidebar_sticky__W61mq"><span style="display:block;height:1.5rem;flex-shrink:0"></span><div class="ArticleSidebarToc_toc__dUPn8"><div class="ArticleSidebarToc_tocTitle__A3VjO">目次</div><div class="ArticleToc_toc__WF75u"><ol class="ol-depth-1"><li><a href="#2024%2F05%2F07-%E8%BF%BD%E8%A8%98">2024/05/07 追記</a></li><li><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></li><li><a href="#%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89">結論から</a></li><li><a href="#%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83">デモ環境</a></li><li><a href="#%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9">既存の方法の欠点</a><ol class="ol-depth-2"><li><a href="#(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7">(1) 完全にHTMLで作って、ブラウザの印刷機能で印刷</a></li><li><a href="#(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B">(2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換</a></li></ol></li><li><a href="#%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9">ベストプラクティスの具体的なやり方</a><ol class="ol-depth-2"><li><a href="#1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B">1. Adobe XDやFigmaを使って帳票をデザインし、SVG形式でエクスポートする</a></li><li><a href="#2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B">2. HTMLにSVGを埋め込み、CSSで印刷に最適化して出力する</a></li><li><a href="#3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B">3. 帳票テンプレート内のプレースホルダーを実際の値に置換する</a></li><li><a href="#4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B">4. 一行テキストの自動縮小・中央寄せ・右寄せをJSで処理する</a></li><li><a href="#5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B">5. 複数行テキストの自動折り返し・自動縮小をマークアップの置換で処理する</a></li></ol></li><li><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF">ユーザーのブラウザに依存したくない場合は</a></li><li><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></li><li><a href="#%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">おまけ:HTML/CSSによる帳票デザインを試す中で試行錯誤したこと</a></li></ol></div></div></div></div></aside></div></div></div></div><div id="related-contents"></div></article><footer class="AppFooter_footer__pqSnY"><div class="Container_wide__ykGLh Container_common__figYY"><div class="AppFooter_inner__uGxbT" data-nosnippet="true"><div class="AppFooter_brandingColumn__BikTT"><a href="/"><svg x="0px" y="0px" viewBox="0 0 377.4 88.3" height="20" width="85"><title>Zenn</title><g fill="#111"><path d="M233,56.8h-39c0.5,3.5,2.2,6.8,4.8,9.2c2.7,2.3,6.2,3.5,9.8,3.4c2.8,0,5.6-0.5,8.2-1.7c2.5-1.1,4.8-2.8,6.5-5l8.2,9.5 c-2.5,3.4-5.7,6.1-9.5,7.9c-4.6,2.2-9.6,3.3-14.7,3.2c-5.7,0.1-11.4-1.2-16.5-4c-4.5-2.5-8.2-6.3-10.7-10.9s-3.8-9.8-3.7-15.1v-2.2 c-0.1-5.7,1.1-11.3,3.5-16.5c2.2-4.7,5.7-8.6,10.1-11.3c4.7-2.8,10.1-4.2,15.5-4.1c5.2-0.1,10.3,1.1,14.9,3.7 c4.1,2.5,7.4,6.2,9.4,10.5c2.2,5.1,3.3,10.5,3.2,16.1V56.8z M216.1,43.9c0.1-2.9-0.9-5.7-2.8-7.9c-1.8-1.9-4.4-2.9-7.9-2.9 c-2.9-0.1-5.8,1.1-7.7,3.2c-2,2.6-3.3,5.7-3.6,9h22V43.9z"></path><path d="M128.3,67.9h36.1v14.7h-56.9V72l35.8-54.3h-36.2V2.9h56.6v10.4L128.3,67.9z"></path><path d="M248.8,50.7c0-19.1,12.7-29.2,28.2-29.2s27.9,10.1,27.9,29.2V82h-16V51.4c0-10.6-4.8-16.1-12-16.1s-12.4,5.5-12.4,16.1 v30.7h-15.8L248.8,50.7L248.8,50.7z"></path><path d="M320.3,50.7c0-19.1,12.7-29.2,28.2-29.2s27.9,10.1,27.9,29.2V82h-16V51.4c0-10.6-4.8-16.1-12-16.1S336,40.8,336,51.4v30.7 h-15.8L320.3,50.7L320.3,50.7z"></path></g><path fill="#3EA8FF" class="st0" d="M2.4,83.3h17c0.9,0,1.7-0.5,2.2-1.2L68.4,5.2C69,4.2,68.3,3,67.1,3H51c-0.8,0-1.5,0.4-1.9,1.1L1.6,81.9 C1.3,82.5,1.7,83.3,2.4,83.3z"></path><path fill="#3EA8FF" class="st0" d="M61,82.1l22.1-35.5c0.7-1.1-0.1-2.5-1.4-2.5H65.7c-0.6,0-1.2,0.3-1.5,0.8L41.5,81.2c-0.6,0.9,0.1,2.1,1.2,2.1 h16.3C59.8,83.3,60.6,82.9,61,82.1z"></path></svg></a><p class="AppFooter_siteDescription__NWGP2">エンジニアのための<br aria-hidden="true"/>情報共有コミュニティ</p></div><div class="AppFooter_navColumns__ahV9g"><nav class="AppFooter_navColumn__47qTk"><h4 class="AppFooter_navColumnTitle__vVeiQ">About</h4><ul><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/about">Zennについて</a></div></li><li><a href="https://classmethod.jp" rel="nofollow noopener noreferrer" target="_blank">運営会社</a></li><li><a href="https://info.zenn.dev" rel="nofollow noopener noreferrer" target="_blank">お知らせ・リリース</a></li></ul></nav><nav class="AppFooter_navColumn__47qTk"><h4 class="AppFooter_navColumnTitle__vVeiQ">Guides</h4><ul><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/manual">使い方</a></div></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/biz-lp">法人向けメニュー</a><span class="NewLabel_newLabelSecondary__6Iy_T NewLabel_newLabel__Xva_r">New</span></div></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/publications">Publication / Pro</a></div></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/faq">よくある質問</a></div></li></ul></nav><nav class="AppFooter_navColumn__47qTk"><h4 class="AppFooter_navColumnTitle__vVeiQ">Links</h4><ul><li><a href="https://twitter.com/zenn_dev" rel="nofollow noopener noreferrer" target="_blank">X(Twitter)</a></li><li><a href="https://github.com/zenn-dev" rel="nofollow noopener noreferrer" target="_blank">GitHub</a></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/mediakit">メディアキット</a></div></li></ul></nav><nav class="AppFooter_navColumn__47qTk"><h4 class="AppFooter_navColumnTitle__vVeiQ">Legal</h4><ul><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/terms">利用規約</a></div></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/privacy">プライバシーポリシー</a></div></li><li><div style="display:flex;gap:0.5rem;flex-direction:row;align-items:center;flex-wrap:nowrap"><a href="/terms/transaction-law">特商法表記</a></div></li></ul></nav></div></div><div class="AppFooter_copyright__J_Jbe" data-nosnippet="true"><div class="AnnouncementPopUp_hiddenWrapper__ThDWT"><div aria-hidden="true" class="PopUp_popup__lIgfz AnnouncementPopUp_container__Pb5q2"><div></div></div></div><a href="https://classmethod.jp/"><img alt="Classmethod inc." height="25" loading="lazy" src="https://static.zenn.studio/images/classmethod-logo-small.svg" width="115"/></a></div></div></footer><div id="modal-portal"></div></div><script id="__NEXT_DATA__" type="application/json" nonce="5DwkwkIqclBVZQwDT+Cav3iYDRwuJf3Yl6WI+rkceu0=">{"props":{"pageProps":{"article":{"id":105830,"postType":"Article","title":"ついに、Webアプリでの帳票印刷のベストプラクティスを編み出しました","slug":"1f1572cfd2e375","commentsCount":0,"likedCount":131,"bookmarkedCount":0,"bodyLettersCount":13281,"articleType":"tech","emoji":"🐘","isSuspendingPrivate":false,"publishedAt":"2021-06-05T00:00:00.000+09:00","bodyUpdatedAt":"2024-05-07T10:58:00.615+09:00","sourceRepoUpdatedAt":"2024-05-07T10:58:00.607+09:00","pinned":false,"path":"/ttskch/articles/1f1572cfd2e375","bodyHtml":"\u003caside class=\"msg message\"\u003e\u003cspan class=\"msg-symbol\"\u003e!\u003c/span\u003e\u003cdiv class=\"msg-content\"\u003e\n\u003cp\u003eこの記事は、2021-06-05に別のブログ媒体に投稿して \u003ca href=\"https://b.hatena.ne.jp/entry/s/blog.ttskch.com/web-app-pdf-printing-best-practice/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eはてなブックマークで1,000以上ブックマークされた\u003c/a\u003e 記事のアーカイブです。\u003c/p\u003e\n\u003c/div\u003e\u003c/aside\u003e\n\u003caside class=\"msg message\"\u003e\u003cspan class=\"msg-symbol\"\u003e!\u003c/span\u003e\u003cdiv class=\"msg-content\"\u003e\n\u003cp\u003eこの記事で紹介した手順をライブラリ化して公開しました🎉\u003cbr\u003e\n\u003ca href=\"https://zenn.dev/ttskch/articles/8ee0eaaabf0657\" target=\"_blank\"\u003eこちらの別記事\u003c/a\u003e で使い方など詳しくご紹介していますので、ぜひご参照ください!\u003c/p\u003e\n\u003c/div\u003e\u003c/aside\u003e\n\u003ch1 id=\"2024%2F05%2F07-%E8%BF%BD%E8%A8%98\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#2024%2F05%2F07-%E8%BF%BD%E8%A8%98\" aria-hidden=\"true\"\u003e\u003c/a\u003e 2024/05/07 追記\u003c/h1\u003e\n\u003cp\u003e最新の登壇スライドバージョンはこちらです。\u003c/p\u003e\n\u003cspan class=\"embed-block embed-speakerdeck\"\u003e\u003ciframe src=\"https://speakerdeck.com/player/9ada4bc0ff194c9ab500063c0a75dec5\" scrolling=\"no\" allowfullscreen allow=\"encrypted-media\" loading=\"lazy\"\u003e\u003c/iframe\u003e\u003c/span\u003e\n\u003cp\u003e登壇時の様子がYouTubeに上がっているのでよろしければあわせてご覧ください。\u003c/p\u003e\n\u003cp\u003e\u003cspan class=\"embed-block embed-youtube\"\u003e\u003ciframe src=\"https://www.youtube-nocookie.com/embed/tIxd8C5IDLQ\" allow=\"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen loading=\"lazy\"\u003e\u003c/iframe\u003e\u003c/span\u003e\u003ca href=\"https://www.youtube.com/watch?v=tIxd8C5IDLQ\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://www.youtube.com/watch?v=tIxd8C5IDLQ\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\" aria-hidden=\"true\"\u003e\u003c/a\u003e はじめに\u003c/h1\u003e\n\u003cul\u003e\n\u003cli\u003e言い切りタイトルすみません\u003c/li\u003e\n\u003cli\u003e僕を含む一定数の人にとって現時点でのベストプラクティスとなりうる手法という意味で紹介しています\u003c/li\u003e\n\u003cli\u003e極めてシビアな帳票出力の世界にいる人から見ると使い物にならない内容かもしれないと思います\u003c/li\u003e\n\u003cli\u003e帳票印刷の世界では \u003ca href=\"https://www.wingarc.com/product/svf/lineup/web_pdf.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eSVF\u003c/a\u003e というサービスが有名らしいです。が、こういった外部サービスは使わずに自力で実装するというのがこの記事の前提です\u003c/li\u003e\n\u003cli\u003e動的に明細行の数が増減する連票はこの記事の解説では考慮していませんが、追加で実装するのはそれほど難しくないということは読んでいただければ分かるかなと思います\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89\" aria-hidden=\"true\"\u003e\u003c/a\u003e 結論から\u003c/h1\u003e\n\u003cp\u003e\u003cspan class=\"embed-block zenn-embedded zenn-embedded-tweet\"\u003e\u003ciframe id=\"zenn-embedded__e2590ee51dfbc\" src=\"https://embed.zenn.studio/tweet#zenn-embedded__e2590ee51dfbc\" data-content=\"https%3A%2F%2Ftwitter.com%2Fttskch%2Fstatus%2F1397926291127508993\" frameborder=\"0\" scrolling=\"no\" loading=\"lazy\"\u003e\u003c/iframe\u003e\u003c/span\u003e\u003ca href=\"https://twitter.com/ttskch/status/1397926291127508993\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://twitter.com/ttskch/status/1397926291127508993\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e僕が考える現時点でのWebアプリでの帳票印刷のベストプラクティスは、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://www.adobe.com/jp/products/xd.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eAdobe XD\u003c/a\u003e や \u003ca href=\"https://www.figma.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eFigma\u003c/a\u003e で帳票のレイアウトをデザインして\u003c/li\u003e\n\u003cli\u003eそれをSVG形式でエクスポートしたものをテンプレートとしてアプリで読み込み\u003c/li\u003e\n\u003cli\u003eプレースホルダーに当たる文字列を置換した上でSVGをそのままHTMLに埋め込んで出力し\u003c/li\u003e\n\u003cli\u003eSVGの外側のレイアウト(プレビュー画面の見え方、印刷時のページ設定)だけCSSで整え\u003c/li\u003e\n\u003cli\u003e文字の自動縮小・自動折り返し等を別途実装しておき\u003c/li\u003e\n\u003cli\u003ePDF出力やプリンタでの印刷はブラウザの印刷機能を使ってもらう(ブラウザで見えているままが印刷される)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eです。\u003c/p\u003e\n\u003cp\u003e色々試しましたが、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eピクセル単位で細かく帳票をデザインできる(しかも簡単に)\u003c/li\u003e\n\u003cli\u003e帳票デザインの保守性が高い(修正が容易)\u003c/li\u003e\n\u003cli\u003e印刷時に見た目が一切崩れない\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという条件を満たせる方法は今のところこれしかないという結論です。\u003c/p\u003e\n\u003cp\u003eこの方法を使うと、例えばこんな感じの帳票も簡単かつ保守性高く作れます👍👍👍\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://user-images.githubusercontent.com/4360663/120876914-9bb2b280-c5ee-11eb-9427-1bd98fa6ba21.png\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003ch1 id=\"%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83\" aria-hidden=\"true\"\u003e\u003c/a\u003e デモ環境\u003c/h1\u003e\n\u003cp\u003e下記に実際にアプリを動かせるデモ環境を用意しました。ぜひ触ってみてください。(Herokuの無料プランなので初回起動重いです)\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://svg-paper-example.herokuapp.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://svg-paper-example.herokuapp.com/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eまた、このデモのソースコードは以下のリポジトリで公開していますので、あわせてご参照ください。\u003cbr\u003e\nデモはPHP(Laravel)で作ってありますが、知見そのものは他の言語・フレームワークでもそのまま流用できるかと思います。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/ttskch/svg-paper-example\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://github.com/ttskch/svg-paper-example\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9\" aria-hidden=\"true\"\u003e\u003c/a\u003e 既存の方法の欠点\u003c/h1\u003e\n\u003cp\u003eさて、実装方法について説明する前に、既存の方法のどこがダメだったのというのを簡単に話しておきたいと思います。\u003c/p\u003e\n\u003cp\u003e僕の観測している範囲だと、Webアプリでの帳票出力の実装には以下の2つの方法が採用されていることが多そうかなと思っています。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e(1) 完全にHTMLで作って、ブラウザの印刷機能で印刷\u003c/li\u003e\n\u003cli\u003e(2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7\" aria-hidden=\"true\"\u003e\u003c/a\u003e (1) 完全にHTMLで作って、ブラウザの印刷機能で印刷\u003c/h2\u003e\n\u003cp\u003eはじめはこの方法ですんなり行けると考えていました。\u003c/p\u003e\n\u003cp\u003e下記のような偉大なる先人の知恵があったので、慣れ親しんだHTML/CSSで帳票をデザインするだけだと。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://qiita.com/cognitom/items/d39d5f19054c8c8fd592\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eそろそろ真面目に、HTMLで帳票を描く話をしようか - Qiita\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://deep-space.blue/web/1858\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e【帳票CSS】A4印刷用のHTMLを作ろう(Chrome用) | deep-space.blue\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eしかし実際にやってみると、帳票の細かなデザインをHTML/CSSで再現するのがひたすらに面倒臭く、お客さんの要望を細かいところまで再現していった結果、非常に難読なHTML/CSSが出来上がりました😓\u003c/p\u003e\n\u003cp\u003e考えてみれば、帳票のデザインって多くの場合A4一枚にピッタリ収まることが大前提になっていて、Webにおけるページレイアウトのセオリーとはかけ離れているので、保守性を維持しながらこれを作るのは相当難しいです。\u003c/p\u003e\n\u003cp\u003e例えばテーブル(表)1つとっても、普段それほど使わない \u003ccode\u003erowspan\u003c/code\u003e \u003ccode\u003ecolspan\u003c/code\u003e を大量に使ってめちゃくちゃ複雑なレイアウトのテーブルを組み立てることとかを普通に要求されます。作るだけならまだしも、その後仕様変更でこのテーブルの中にセルを追加(しかも全体がちゃんとA4に収まるように)しないといけなくなったときのことを考えると、遠い目にならざるを得ません。\u003c/p\u003e\n\u003cp\u003e特に僕のように普段BootstrapなどのCSSフレームワークのレールに乗っかったHTMLしか書いていない人間にはとにかく苦行でしかありませんでした。(普段から複雑なHTMLを書いているデザイナーさんとかにとっては別にしんどくないのかもしれません)\u003c/p\u003e\n\u003ch2 id=\"(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B\" aria-hidden=\"true\"\u003e\u003c/a\u003e (2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換\u003c/h2\u003e\n\u003cp\u003eHTML/CSSのメンテが大変すぎるということが分かったので、思い切ってExcelファイルをテンプレートにする方法を試してみました。\u003c/p\u003e\n\u003cp\u003e帳票を視覚的にデザインできますし、Excelなら(Windows版ならWordも)「縮小して全体を表示」というお馴染みの機能があるのでフォントの縮小についても何も考えなくてよさそうです。\u003c/p\u003e\n\u003cp\u003e調べてみると、\u003ca href=\"https://ja.libreoffice.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eLibreOffice\u003c/a\u003e のヘッドレスモードを使えばCLIでExcelファイルのPDFへの変換ができるというこを知り、これなら行けるのではと思いました。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://qiita.com/hirohiro77/items/942eb461e8f4727e4b38\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eLibreOfficeでドキュメントコンバータを作ろう - Qiita\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eところがこの方法にも色々と難があり、特に\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eExcelで帳票の細かいデザインをしようとすると、行の高さ・列の幅を極端に小さくした \u003cstrong\u003e地獄のExcel方眼紙\u003c/strong\u003e にならざるを得ない\u003c/li\u003e\n\u003cli\u003eExcelをPDFに変換する際に多少 \u003cstrong\u003e見た目が崩れる\u003c/strong\u003e\n\u003c/li\u003e\n\u003cli\u003e同じLibreOfficeでも \u003cstrong\u003eMac版とLinux版でPDFの出力結果が微妙に異なる\u003c/strong\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eの3点が致命的でした。\u003c/p\u003e\n\u003cp\u003eExcel方眼紙は、セルの大きさがフォント1文字分ぐらいならまだギリ許せる(?)のですが、ピクセル単位に近い微妙なデザインを実現しようと思うと地獄のようにセルを小さくする必要が出てきて心が折れます。\u003c/p\u003e\n\u003cp\u003eLibreOfficeによるPDFへの変換が完璧でない点も、多くの案件において許容不可能でしょう。\u003c/p\u003e\n\u003ch1 id=\"%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9\" aria-hidden=\"true\"\u003e\u003c/a\u003e ベストプラクティスの具体的なやり方\u003c/h1\u003e\n\u003cp\u003eというわけでたどり着いたのが、冒頭でご紹介した方法です。\u003c/p\u003e\n\u003cp\u003e上記2つの方法で満たせなかった\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eピクセル単位で細かく帳票をデザインできる(しかも簡単に)\u003c/li\u003e\n\u003cli\u003e帳票デザインの保守性が高い(修正が容易)\u003c/li\u003e\n\u003cli\u003e印刷時に見た目が一切崩れない\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという要求を \u003cstrong\u003e完璧に満たしてくれる\u003c/strong\u003e のがこのSVGを使った方法です👍\u003c/p\u003e\n\u003cp\u003e以下、順を追って具体的なやり方を解説していきます。\u003c/p\u003e\n\u003ch2 id=\"1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 1. Adobe XDやFigmaを使って帳票をデザインし、SVG形式でエクスポートする\u003c/h2\u003e\n\u003cp\u003eまず、\u003ca href=\"https://www.adobe.com/jp/products/xd.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eAdobe XD\u003c/a\u003e や \u003ca href=\"https://www.figma.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eFigma\u003c/a\u003e といったUI/UXデザインツールを使って帳票をデザインし、それをSVG形式でエクスポートします。\u003c/p\u003e\n\u003ch3 id=\"adobe-xd%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#adobe-xd%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86\" aria-hidden=\"true\"\u003e\u003c/a\u003e Adobe XDでのSVGエクスポートの手順\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eファイル \u0026gt; 書き出し \u0026gt; すべてのアートボード\u003c/code\u003e でファイル保存のダイアログが出ます。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://res.cloudinary.com/zenn/image/fetch/s--4Fpnf60s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr77qwtl1rj60yk0p4act02.jpg\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cp\u003eここで \u003ccode\u003eフォーマット\u003c/code\u003e を \u003ccode\u003eSVG\u003c/code\u003e にして保存すればOKです。\u003c/p\u003e\n\u003cp\u003e帳票内で画像を使う場合は、下図のように \u003ccode\u003e画像を保存\u003c/code\u003e の設定を \u003ccode\u003e埋め込み\u003c/code\u003e にする必要があります。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://res.cloudinary.com/zenn/image/fetch/s--nE2hs89w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr6hhsb9grj31ag0oqjtl.jpg\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e埋め込み\u003c/code\u003e にすると画像はbase64エンコードされてデータURLとして埋め込まれます。\u003c/p\u003e\n\u003ch3 id=\"figma%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#figma%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86\" aria-hidden=\"true\"\u003e\u003c/a\u003e FigmaでのSVGエクスポートの手順\u003c/h3\u003e\n\u003cp\u003eフレーム単位で選択して、右カラム最下部の \u003ccode\u003eExport\u003c/code\u003e メニューでエクスポートします。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://res.cloudinary.com/zenn/image/fetch/s--CtTF4k8_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_1200/https://tva1.sinaimg.cn/large/008i3skNgy1gr77yr28tjj31nj0u04ow.jpg\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cp\u003eこの際、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ccode\u003eInclude \"id\" Attribute\u003c/code\u003e にチェックを入れる\u003c/li\u003e\n\u003cli\u003e\n\u003ccode\u003eOutline Text\u003c/code\u003e の \u003cstrong\u003eチェックを外す\u003c/strong\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eの2点を忘れないようにしてください。\u003c/p\u003e\n\u003cp\u003e後述するJSによる調整の段階で \u003ccode\u003eid\u003c/code\u003e 属性を使いたいのと、そもそもテキストの置換を行うために文字列を \u003ccode\u003e\u0026lt;path\u0026gt;\u003c/code\u003e タグではなく \u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e タグで出力してほしいのでこの設定が必要です。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eデモアプリのソースコードの対応するコミットは \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/2c0395b8dc1249c6b75caa7638452c211c872778\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこちら\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 2. HTMLにSVGを埋め込み、CSSで印刷に最適化して出力する\u003c/h2\u003e\n\u003cp\u003eSVG形式のテキストファイルができたので、まずはこのテキストをそのままHTMLに埋め込んで画面に出力します。\u003c/p\u003e\n\u003cp\u003eその際、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e印刷時にA4縦ぴったりで出力されるように\u003c/li\u003e\n\u003cli\u003e画面表示時に印刷プレビューっぽい見た目で表示されるように\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eの2点を実現するために多少のCSSを書く必要があります。\u003c/p\u003e\n\u003cp\u003e具体的には以下のような内容でOKです。(これはSCSSで書いてあります)\u003c/p\u003e\n\u003cdiv class=\"code-block-container\"\u003e\u003cpre class=\"language-scss\"\u003e\u003ccode class=\"language-scss\"\u003e\u003cspan class=\"token atrule\"\u003e\u003cspan class=\"token rule\"\u003e@page\u003c/span\u003e\u003c/span\u003e \u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003esize\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e A4 portrait\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003emargin\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e \u003cspan class=\"token comment\"\u003e// ヘッダー・フッターが出力されないように\u003c/span\u003e\n\u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"token selector\"\u003e* \u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003emargin\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003epadding\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003euser-select\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e none\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n\u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"token selector\"\u003ebody \u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ewidth\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e210\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ecolor-adjust\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e exact\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token selector\"\u003e\u0026gt; svg \u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ewidth\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e210\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003eheight\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e295.5\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e \u003cspan class=\"token comment\"\u003e// 297mmだと2ページ目にはみ出してしまうので微調整\u003c/span\u003e\n \u003cspan class=\"token property\"\u003epage-break-after\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e always\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\n\u003cspan class=\"token comment\"\u003e// プレビュー用\u003c/span\u003e\n\u003cspan class=\"token atrule\"\u003e\u003cspan class=\"token rule\"\u003e@media\u003c/span\u003e screen\u003c/span\u003e \u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token selector\"\u003ebody \u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ebackground\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token hexcode color\"\u003e#ccc\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003emargin\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e0\u003c/span\u003e auto\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token selector\"\u003e\u0026gt; svg \u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ebackground\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token hexcode color\"\u003e#fff\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003ebox-shadow\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e0\u003c/span\u003e \u003cspan class=\"token number\"\u003e.5\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e \u003cspan class=\"token number\"\u003e2\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e \u003cspan class=\"token color\"\u003e\u003cspan class=\"token function\"\u003ergba\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e(\u003c/span\u003e\u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e,\u003c/span\u003e\u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e,\u003c/span\u003e\u003cspan class=\"token number\"\u003e0\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e,\u003c/span\u003e\u003cspan class=\"token number\"\u003e.3\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e)\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token property\"\u003emargin-top\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e:\u003c/span\u003e \u003cspan class=\"token number\"\u003e5\u003c/span\u003e\u003cspan class=\"token unit\"\u003emm\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e;\u003c/span\u003e\n \u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n \u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eこのCSSの意味については今回は詳しい解説は割愛します🙏\u003c/p\u003e\n\u003cp\u003e以下の参考記事を読んでいただければ理解できると思います。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e参考:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://qiita.com/cognitom/items/d39d5f19054c8c8fd592\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eそろそろ真面目に、HTMLで帳票を描く話をしようか - Qiita\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://deep-space.blue/web/1858\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e【帳票CSS】A4印刷用のHTMLを作ろう(Chrome用) | deep-space.blue\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eこの時点で、下図のように \u003cstrong\u003eAdobe XDでデザインした帳票がそのままの見た目で印刷プレビューっぽく画面に表示でき、ブラウザの印刷機能を使えばそのままの見た目でPDF出力もできる\u003c/strong\u003e という状態まで来ました👍\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://user-images.githubusercontent.com/4360663/120877569-4082bf00-c5f2-11eb-8e0e-2ca92d0a7e80.png\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eデモアプリのソースコードの対応するコミットは \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/da6ec2b41035a42d88cd1b29d0b60f67a5211a5c\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこちら\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 3. 帳票テンプレート内のプレースホルダーを実際の値に置換する\u003c/h2\u003e\n\u003cp\u003eこの時点の出力内容は、デザインの時点で埋め込んでおいた \u003ccode\u003e%顧客名%\u003c/code\u003e のようなプレースホルダー文字列になっているので、出力する前にこれを実際の値に置換する処理を書きます。\u003c/p\u003e\n\u003cp\u003ePHPの場合は、普通に \u003ca href=\"https://www.php.net/manual/ja/function.str-replace.php\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003estr_replace()\u003c/code\u003e\u003c/a\u003e で一つひとつ置換していけばOKです。画像を差し替える場合は \u003ccode\u003exlink:href=\"data:image/png;base64,略\"\u003c/code\u003e といった画像URL部分を置換します。\u003c/p\u003e\n\u003cp\u003eなお、\u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e タグの \u003ccode\u003efont-family\u003c/code\u003e 属性の値も置換する必要があることに注意しましょう。Adobe XDやFigmaでデザインしたときにテキストオブジェクトに設定していたフォントが \u003ccode\u003efont-family\u003c/code\u003e 属性に書かれていますが、フォント自体が埋め込まれているわけではないので、別途ロードしたWebフォントに置き換えるか、明朝体とゴシック体の使い分けぐらいでいいなら \u003ccode\u003eserif\u003c/code\u003e \u003ccode\u003esans-serif\u003c/code\u003e に置き換えてユーザーの環境に任せてしまってもよいかと思います。\u003c/p\u003e\n\u003cp\u003eこの時点の出力結果は以下のような感じです。内容は実際の値に置換されましたが、テキストが枠をはみ出していますし、金額を右寄せにしたりもしたい感じですね。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://user-images.githubusercontent.com/4360663/120879247-036ffa00-c5fd-11eb-819b-1333f51d3f5d.png\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eデモアプリのソースコードの対応するコミットは \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/9ed38f15381e2e1efbd74cf9e76a9c1320172133\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこちら\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 4. 一行テキストの自動縮小・中央寄せ・右寄せをJSで処理する\u003c/h2\u003e\n\u003cp\u003eJavaScriptから \u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e 要素を( \u003ccode\u003eid\u003c/code\u003e 属性で指定して)いじることで、文字の自動縮小や配置の調整が可能です👍\u003c/p\u003e\n\u003cp\u003eそれぞれ具体的な方法を説明します。\u003c/p\u003e\n\u003ch3 id=\"%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA\" aria-hidden=\"true\"\u003e\u003c/a\u003e 縮小して全体を表示\u003c/h3\u003e\n\u003cp\u003eまずは、Excelにおける \u003ccode\u003e縮小して全体を表示\u003c/code\u003e 相当の挙動をJavaScriptで実装します。\u003c/p\u003e\n\u003cp\u003eSVGの \u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e \u003ccode\u003e\u0026lt;tspan\u0026gt;\u003c/code\u003e 要素には \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/textLength\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003etextLength\u003c/code\u003e\u003c/a\u003e という属性があり、テキスト全体の幅を指定することができます。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etextLength\u003c/code\u003e をコンテンツ幅よりも小さく設定すると、デフォルトの挙動では文字のサイズは変わらず字間が無理矢理詰められて文字と文字が重なってしまうのですが、\u003ca href=\"https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/lengthAdjust\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003elengthAdjust\u003c/code\u003e\u003c/a\u003e 属性に \u003ccode\u003espacingAndGlyphs\u003c/code\u003e を設定することでこの挙動を変更することができます。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003espacingAndGlyphs\u003c/code\u003e は、これ以上字間を詰められなくなると文字自体の幅を縮小してくれます。高さは変わらず幅だけが縮小されるので、狭い領域にめちゃくちゃ長いテキストを入れてしまうと異常に縦長な文字になってしまいますが、その状況では仮に縦横比を維持したまま縮小されたとしても字が小さすぎて読めないと思いますし、帳票印刷という文脈ではほぼ気にしなくていいかなと思います。\u003c/p\u003e\n\u003cp\u003e注意すべきは、\u003ccode\u003etextLength\u003c/code\u003e で指定した幅よりもコンテンツの幅のほうが小さい場合、逆に拡大されてしまうことです。これは、\u003c/p\u003e\n\u003cdiv class=\"code-block-container\"\u003e\u003cpre class=\"language-js\"\u003e\u003ccode class=\"language-js\"\u003e\u003cspan class=\"token keyword control-flow\"\u003eif\u003c/span\u003e \u003cspan class=\"token punctuation\"\u003e(\u003c/span\u003eelem\u003cspan class=\"token punctuation\"\u003e.\u003c/span\u003e\u003cspan class=\"token property-access\"\u003eclientWidth\u003c/span\u003e \u003cspan class=\"token operator\"\u003e\u0026gt;\u003c/span\u003e config\u003cspan class=\"token punctuation\"\u003e.\u003c/span\u003e\u003cspan class=\"token property-access\"\u003etextLength\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e)\u003c/span\u003e \u003cspan class=\"token punctuation\"\u003e{\u003c/span\u003e\n elem\u003cspan class=\"token punctuation\"\u003e.\u003c/span\u003e\u003cspan class=\"token method function property-access\"\u003esetAttribute\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e(\u003c/span\u003e\u003cspan class=\"token string\"\u003e'textLength'\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e,\u003c/span\u003e config\u003cspan class=\"token punctuation\"\u003e.\u003c/span\u003e\u003cspan class=\"token property-access\"\u003etextLength\u003c/span\u003e\u003cspan class=\"token punctuation\"\u003e)\u003c/span\u003e\n\u003cspan class=\"token punctuation\"\u003e}\u003c/span\u003e\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eといった具合にコンテンツ幅が指定の幅を超えているときのみ \u003ccode\u003etextLength\u003c/code\u003e を適用するようにすればよいでしょう。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e一応こんな議論もあるようです。\u003cbr\u003e\n\u003ca href=\"https://github.com/w3c/svgwg/issues/341\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003elengthAdjust values just for shrinking · Issue #341 · w3c/svgwg\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eなお、Firefoxでは\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eインライン要素に対しては \u003ccode\u003eclientWidth\u003c/code\u003e で幅が取得できない(常に0が返る)という \u003ca href=\"https://developer.mozilla.org/ja/docs/Web/API/Element/clientWidth\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e仕様\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003ccode\u003etspan\u003c/code\u003e 要素に対して \u003ccode\u003etextLength\u003c/code\u003e \u003ccode\u003elengthAdjust\u003c/code\u003e 属性が機能しないという \u003ca href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=890692\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e既知のバグ\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eがあるため、追加で \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/5c93d73377ee7bd6a6f0197f878075599294b24e\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこのような対応\u003c/a\u003e が必要になります。\u003c/p\u003e\n\u003ch3 id=\"%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 中央寄せ・右寄せ\u003c/h3\u003e\n\u003cp\u003e次に中央寄せ・右寄せについてですが、これは \u003ca href=\"https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003etext-anchor\u003c/code\u003e\u003c/a\u003e 属性を使うことで実現可能です。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etext-anchor\u003c/code\u003e 属性を \u003ccode\u003emiddle\u003c/code\u003e にすれば、基準となるx座標にテキストの中心が来るようになり、\u003ccode\u003eend\u003c/code\u003e にすれば、基準となるx座標にテキストの末尾が来るようになります。特に指定しなければデフォルトで \u003ccode\u003estart\u003c/code\u003e という値になり、基準となるx座標にテキストの先頭が来るようになります。\u003c/p\u003e\n\u003cp\u003e「基準となるx座標」とは、\u003ccode\u003etext\u003c/code\u003e 要素の \u003ca href=\"https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/transform\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003etransform\u003c/code\u003e 属性\u003c/a\u003e(の \u003ccode\u003etranslate(\u0026lt;x\u0026gt; [\u0026lt;y\u0026gt;])\u003c/code\u003e 変換関数 )や \u003ccode\u003etspan\u003c/code\u003e 要素の \u003ccode\u003ex\u003c/code\u003e 属性で指定されているx座標のことです。\u003c/p\u003e\n\u003cp\u003eなので、例えば右寄せを実現したい場合は、\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003ccode\u003etext\u003c/code\u003e 要素の \u003ccode\u003etransform\u003c/code\u003e 属性や \u003ccode\u003etspan\u003c/code\u003e 要素の \u003ccode\u003ex\u003c/code\u003e 属性を操作して、右端となるx座標まで移動させる\u003c/li\u003e\n\u003cli\u003eその上で、\u003ccode\u003etext\u003c/code\u003e 要素に \u003ccode\u003etext-anchor=\"end\"\u003c/code\u003e を追加する\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eという操作が必要になります。\u003c/p\u003e\n\u003cp\u003e具体的な実装例は \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/7377c9503152fb8c7c87becfa473db35f09b48d7#diff-e5ed11c1366988d34f3179c4e6d80afaffae57e636aa8617c3527c276ff68b92\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eデモアプリの実際のコード\u003c/a\u003e をご参照ください。\u003c/p\u003e\n\u003cp\u003eこの時点の出力結果は以下のような感じです。(備考とコメント以外の)テキストが枠内に収まり、中央寄せ・右寄せが適切に施されて見た目がだいぶ整いました。\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://user-images.githubusercontent.com/4360663/120879192-99575500-c5fc-11eb-971d-6c917f3f19b0.png\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eデモアプリのソースコードの対応するコミットは \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/7377c9503152fb8c7c87becfa473db35f09b48d7\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこちら\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e 5. 複数行テキストの自動折り返し・自動縮小をマークアップの置換で処理する\u003c/h2\u003e\n\u003cp\u003e最後に、複数行テキストの自動折り返し・自動縮小に対応します。これは正直かなりの力技で対応する必要があります。\u003c/p\u003e\n\u003cp\u003e具体的には、\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e 要素の中に行の数だけ \u003ccode\u003e\u0026lt;tspan\u0026gt;\u003c/code\u003e 要素を挿入して\u003c/li\u003e\n\u003cli\u003e追加挿入した \u003ccode\u003e\u0026lt;tspan\u0026gt;\u003c/code\u003e 要素の \u003ccode\u003ey\u003c/code\u003e 属性を一行分ずつ大きくしていく\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eという処理を実装します。\u003c/p\u003e\n\u003cp\u003eSVGの \u003ccode\u003e\u0026lt;text\u0026gt;\u003c/code\u003e 要素には改行の概念がないため、このような力技が必要になります😓\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSVG 1.1ではこれしかやりようがないのですが、\u003ca href=\"https://www.w3.org/TR/SVGMobile12/index.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eSVG Tiny 1.2\u003c/a\u003e には \u003ca href=\"https://www.w3.org/TR/SVGMobile12/text.html#TextAreaElement\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003e\u0026lt;textArea\u0026gt;\u003c/code\u003e\u003c/a\u003e という要素があり、テキストを自動で折り返してくれるようになっているようです。\u003cbr\u003e\nまた、HTMLの \u003ccode\u003e\u0026lt;br\u0026gt;\u003c/code\u003e に相当する \u003ca href=\"https://www.w3.org/TR/SVGMobile12/text.html#tbreakElement\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003e\u003ccode\u003e\u0026lt;tbreak\u0026gt;\u003c/code\u003e\u003c/a\u003e という要素もあり、かなり簡単に複数行テキストを扱えそうです。\u003cbr\u003e\nしかし残念ながらGoogle Chromeをはじめ主要なブラウザはSVG Tiny 1.2には対応していません。(要出典🙏)\u003c/p\u003e\n\u003cp\u003eブラウザがSVG Tiny 1.2に対応しているかどうかは \u003ca href=\"https://www.w3.org/TR/SVGMobile12/examples/textArea01.svg\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこのページ\u003c/a\u003e で確認することができます。画面を開いてテキストが表示されれば対応しているということのようです。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e実装方法は色々考えられますが、僕は\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e1文字を「一辺が \u003ccode\u003efont-size\u003c/code\u003e の正方形」と見立ててテキストエリアに収まる縦横の文字数を割り出す(プロポーショナルフォントでは誤差が出るけど無視)\u003c/li\u003e\n\u003cli\u003e横方向にその文字数を超える直前で改行を自動で入れて、もともとテキストが持っていた物理的な改行と合わせて最終的な行数を算出する\u003c/li\u003e\n\u003cli\u003eその行数がテキストエリアの縦文字数よりも大きければ、\u003ccode\u003efont-size\u003c/code\u003e を少し小さく(0.95倍)して1に戻る、テキストエリアに収まっていれば4へ\u003c/li\u003e\n\u003cli\u003e各行を \u003ccode\u003e\u0026lt;tspan\u0026gt;\u003c/code\u003e 要素として書き出し、\u003ccode\u003ey\u003c/code\u003e 属性の値は各行 \u003ccode\u003efont-size\u003c/code\u003e 分ずつ大きくなるようにする(厳密には、行間も考慮して1.2倍したり)\u003c/li\u003e\n\u003cli\u003e作った文字列で元のSVGのテキストを置換する\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eという感じの処理を実装しました。実際にはもう少し細かい微調整もしていますが、詳細は \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/4bd52550d354f84f608e252f25b7cc5f2f2aae6a#diff-18076594176699aca0198b10dd509feb8cfad7ead6973051b4e28ecb069b5825\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eデモアプリの実際のコード\u003c/a\u003e をご参照ください🙏\u003c/p\u003e\n\u003cp\u003eここまでで、無事に完全な帳票が出力できるようになりました🙌\u003c/p\u003e\n\u003cp\u003e\u003cimg src=\"https://user-images.githubusercontent.com/4360663/120876914-9bb2b280-c5ee-11eb-9427-1bd98fa6ba21.png\" loading=\"lazy\" class=\"md-img\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://svg-paper-example.herokuapp.com/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eデモアプリ\u003c/a\u003e ではリロードする度に出力するテキストの量がランダムに変わるようになっているので、何度かリロードしてみて、どんな内容でも適切に折り返し・縮小されて枠に収まることを確認してみてください😉\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eデモアプリのソースコードの対応するコミットは \u003ca href=\"https://github.com/ttskch/svg-paper-example/commit/4bd52550d354f84f608e252f25b7cc5f2f2aae6a\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eこちら\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF\" aria-hidden=\"true\"\u003e\u003c/a\u003e ユーザーのブラウザに依存したくない場合は\u003c/h1\u003e\n\u003cp\u003eユーザーのブラウザの印刷機能に依存したくない場合は、 生成したHTMLのPDFへの変換まで含めてアプリ側でやってしまうとよいかと思います。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/fraserxu/electron-pdf\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eelectron-pdf\u003c/a\u003e や \u003ca href=\"https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja#pdf_%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eGoogle Chromeのヘッドレスモード\u003c/a\u003e を使えば特に問題なく実現できるでしょう✋\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eChromeのヘッドレスモードによるPDF出力は、Macなら\u003c/p\u003e\n\u003cdiv class=\"code-block-container\"\u003e\u003cpre class=\"language-bash\"\u003e\u003ccode class=\"language-bash\"\u003e$ /Applications/Google\u003cspan class=\"token punctuation\"\u003e\\\u003c/span\u003e Chrome.app/Contents/MacOS/Google\u003cspan class=\"token punctuation\"\u003e\\\u003c/span\u003e Chrome \u003cspan class=\"token parameter variable\"\u003e--headless\u003c/span\u003e --disable-gpu --print-to-pdf http://svg-paper-example.herokuapp.com/print/estimate/見積書(金額あり)\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp\u003eって感じで簡単に試せます。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB\" aria-hidden=\"true\"\u003e\u003c/a\u003e おわりに\u003c/h1\u003e\n\u003cp\u003eというわけで、僕の考えた最強の帳票印刷について解説しました。\u003c/p\u003e\n\u003cp\u003e解説は長くなりましたが、やっていること自体はそんなに複雑ではないですし、一度作ってしまえば他のプロジェクトにも同じ仕組みを流用できます。\u003c/p\u003e\n\u003cp\u003e今のところ自分の中でこれに勝る方法は見つけられていないので、もっといい方法あるよ!という方がいたらぜひ \u003ca href=\"https://twitter.com/ttskch\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eご一報ください\u003c/a\u003e 💪\u003c/p\u003e\n\u003ch1 id=\"%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8\" aria-hidden=\"true\"\u003e\u003c/a\u003e おまけ:HTML/CSSによる帳票デザインを試す中で試行錯誤したこと\u003c/h1\u003e\n\u003cp\u003eおまけというか単なるメモです。試行錯誤の中で分かったことがいくつかあったので書き残しておきます。\u003c/p\u003e\n\u003cdetails\u003e\u003csummary\u003e表示する\u003c/summary\u003e\u003cdiv class=\"details-content\"\u003e\n\u003ch2 id=\"%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA-%E3%81%AE%E5%AE%9F%E7%8F%BE%E3%81%8C%E6%84%8F%E5%A4%96%E3%81%A8%E5%8E%84%E4%BB%8B\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA-%E3%81%AE%E5%AE%9F%E7%8F%BE%E3%81%8C%E6%84%8F%E5%A4%96%E3%81%A8%E5%8E%84%E4%BB%8B\" aria-hidden=\"true\"\u003e\u003c/a\u003e \u003ccode\u003e縮小して全体を表示\u003c/code\u003e の実現が意外と厄介\u003c/h2\u003e\n\u003cp\u003eExcelにおける \u003ccode\u003e縮小して全体を表示\u003c/code\u003e 相当の挙動はCSSでは実現不可能なので、JSを使う必要があります。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://css-tricks.com/fitting-text-to-a-container/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eFitting Text to a Container | CSS-Tricks\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eこのページなどを参考によさげなライブラリをいくつか(\u003ca href=\"https://github.com/rikschennink/fitty\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003erikschennink/fitty\u003c/a\u003e、\u003ca href=\"https://github.com/STRML/textFit\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003eSTRML/textFit\u003c/a\u003e 等)試してみましたが、どうもこの手のライブラリは \u003cstrong\u003eフォントサイズをコンテンツ幅いっぱいにフィットさせる\u003c/strong\u003e というコンセプトのものばかりで、テキストが多いときには期待どおり縮小されるのですが、\u003cstrong\u003e逆にテキストが少ないときに枠いっぱいまで拡大されてしまう\u003c/strong\u003e という挙動になってしまいました\u003c/p\u003e\n\u003cp\u003eやりたいのはもちろん縮小のみで拡大は一切されてほしくないのですが、標準の機能でそのような挙動を実現できるライブラリは見つけることができませんでした。\u003c/p\u003e\n\u003cp\u003eなので、複数行テキストのコンテンツにだけ \u003ccode\u003emaxSize\u003c/code\u003e 的なオプションを使って強引にフォントサイズを固定するようにする必要があります。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/ttskch/status/1395242578191126529\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://twitter.com/ttskch/status/1395242578191126529\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"1%E8%A1%8C%E7%9B%AE%E3%81%8C-colspan-%E3%81%A7%E7%B5%90%E5%90%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A7%E5%90%84%E5%88%97%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#1%E8%A1%8C%E7%9B%AE%E3%81%8C-colspan-%E3%81%A7%E7%B5%90%E5%90%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A7%E5%90%84%E5%88%97%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95\" aria-hidden=\"true\"\u003e\u003c/a\u003e 1行目が \u003ccode\u003ecolspan\u003c/code\u003e で結合されているテーブルで各列の幅を固定する方法\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/ttskch/status/1394812266822864901\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://twitter.com/ttskch/status/1394812266822864901\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"flexbox%E3%81%A7%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%82%E3%81%86%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E4%BC%B8%E7%B8%AE%E3%81%95%E3%81%9B%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9\"\u003e\n\u003ca class=\"header-anchor-link\" href=\"#flexbox%E3%81%A7%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E5%B9%85%E3%82%92%E5%9B%BA%E5%AE%9A%E3%81%97%E3%81%A6%E3%82%82%E3%81%86%E4%B8%80%E6%96%B9%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E4%BC%B8%E7%B8%AE%E3%81%95%E3%81%9B%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9\" aria-hidden=\"true\"\u003e\u003c/a\u003e flexboxで一方のカラムの幅を固定してもう一方のカラムを伸縮させる場合のベストプラクティス\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/ttskch/status/1394809869543231493\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"\u003ehttps://twitter.com/ttskch/status/1394809869543231493\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\u003c/details\u003e\n","ogImageUrl":"https://res.cloudinary.com/zenn/image/upload/s--qDOTW7FI--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:%25E3%2581%25A4%25E3%2581%2584%25E3%2581%25AB%25E3%2580%2581Web%25E3%2582%25A2%25E3%2583%2597%25E3%2583%25AA%25E3%2581%25A7%25E3%2581%25AE%25E5%25B8%25B3%25E7%25A5%25A8%25E5%258D%25B0%25E5%2588%25B7%25E3%2581%25AE%25E3%2583%2599%25E3%2582%25B9%25E3%2583%2588%25E3%2583%2597%25E3%2583%25A9%25E3%2582%25AF%25E3%2583%2586%25E3%2582%25A3%25E3%2582%25B9%25E3%2582%2592%25E7%25B7%25A8%25E3%2581%25BF%25E5%2587%25BA%25E3%2581%2597%25E3%2581%25BE%25E3%2581%2597%25E3%2581%259F%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:ttskch%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2RhY2Y2ZmZjMDcuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png","toc":[{"id":"2024%2F05%2F07-%E8%BF%BD%E8%A8%98","text":"2024/05/07 追記","level":1,"children":[]},{"id":"%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB","text":"はじめに","level":1,"children":[]},{"id":"%E7%B5%90%E8%AB%96%E3%81%8B%E3%82%89","text":"結論から","level":1,"children":[]},{"id":"%E3%83%87%E3%83%A2%E7%92%B0%E5%A2%83","text":"デモ環境","level":1,"children":[]},{"id":"%E6%97%A2%E5%AD%98%E3%81%AE%E6%96%B9%E6%B3%95%E3%81%AE%E6%AC%A0%E7%82%B9","text":"既存の方法の欠点","level":1,"children":[{"id":"(1)-%E5%AE%8C%E5%85%A8%E3%81%ABhtml%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%A6%E3%80%81%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E5%8D%B0%E5%88%B7%E6%A9%9F%E8%83%BD%E3%81%A7%E5%8D%B0%E5%88%B7","text":"(1) 完全にHTMLで作って、ブラウザの印刷機能で印刷","level":2,"children":[]},{"id":"(2)-excel%E3%82%84word%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%85%83%E3%81%AB%E4%B8%80%E6%97%A6excel%E3%82%84word%E3%81%A7%E5%B8%B3%E7%A5%A8%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%82%92libreoffice%E3%81%AE%E3%83%98%E3%83%83%E3%83%89%E3%83%AC%E3%82%B9%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AA%E3%81%A9%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6pdf%E3%81%AB%E5%A4%89%E6%8F%9B","text":"(2) ExcelやWordのテンプレートを元に一旦ExcelやWordで帳票を出力し、それをLibreOfficeのヘッドレスモードなどを使ってPDFに変換","level":2,"children":[]}]},{"id":"%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9%E3%81%AE%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E3%82%84%E3%82%8A%E6%96%B9","text":"ベストプラクティスの具体的なやり方","level":1,"children":[{"id":"1.-adobe-xd%E3%82%84figma%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%B8%B3%E7%A5%A8%E3%82%92%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%81%97%E3%80%81svg%E5%BD%A2%E5%BC%8F%E3%81%A7%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B","text":"1. Adobe XDやFigmaを使って帳票をデザインし、SVG形式でエクスポートする","level":2,"children":[{"id":"adobe-xd%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86","text":"Adobe XDでのSVGエクスポートの手順","level":3,"children":[]},{"id":"figma%E3%81%A7%E3%81%AEsvg%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86","text":"FigmaでのSVGエクスポートの手順","level":3,"children":[]}]},{"id":"2.-html%E3%81%ABsvg%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%80%81css%E3%81%A7%E5%8D%B0%E5%88%B7%E3%81%AB%E6%9C%80%E9%81%A9%E5%8C%96%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B","text":"2. HTMLにSVGを埋め込み、CSSで印刷に最適化して出力する","level":2,"children":[]},{"id":"3.-%E5%B8%B3%E7%A5%A8%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E5%86%85%E3%81%AE%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E5%AE%9F%E9%9A%9B%E3%81%AE%E5%80%A4%E3%81%AB%E7%BD%AE%E6%8F%9B%E3%81%99%E3%82%8B","text":"3. 帳票テンプレート内のプレースホルダーを実際の値に置換する","level":2,"children":[]},{"id":"4.-%E4%B8%80%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B%E3%82%92js%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B","text":"4. 一行テキストの自動縮小・中央寄せ・右寄せをJSで処理する","level":2,"children":[{"id":"%E7%B8%AE%E5%B0%8F%E3%81%97%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E8%A1%A8%E7%A4%BA","text":"縮小して全体を表示","level":3,"children":[]},{"id":"%E4%B8%AD%E5%A4%AE%E5%AF%84%E3%81%9B%E3%83%BB%E5%8F%B3%E5%AF%84%E3%81%9B","text":"中央寄せ・右寄せ","level":3,"children":[]}]},{"id":"5.-%E8%A4%87%E6%95%B0%E8%A1%8C%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E8%87%AA%E5%8B%95%E6%8A%98%E3%82%8A%E8%BF%94%E3%81%97%E3%83%BB%E8%87%AA%E5%8B%95%E7%B8%AE%E5%B0%8F%E3%82%92%E3%83%9E%E3%83%BC%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AE%E7%BD%AE%E6%8F%9B%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B","text":"5. 複数行テキストの自動折り返し・自動縮小をマークアップの置換で処理する","level":2,"children":[]}]},{"id":"%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF","text":"ユーザーのブラウザに依存したくない場合は","level":1,"children":[]},{"id":"%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB","text":"おわりに","level":1,"children":[]},{"id":"%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Ahtml%2Fcss%E3%81%AB%E3%82%88%E3%82%8B%E5%B8%B3%E7%A5%A8%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%82%92%E8%A9%A6%E3%81%99%E4%B8%AD%E3%81%A7%E8%A9%A6%E8%A1%8C%E9%8C%AF%E8%AA%A4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8","text":"おまけ:HTML/CSSによる帳票デザインを試す中で試行錯誤したこと","level":1,"children":[]}],"tocEnabled":true,"shouldNoindex":false,"scheduledPublishAt":null,"canSendBadge":true,"status":"published","badges":[],"githubDevUrl":"https://github.dev/ttskch/zenn-content/blob/main/articles/1f1572cfd2e375.md"},"user":{"id":4847,"username":"ttskch","name":"ttskch","avatarSmallUrl":"https://res.cloudinary.com/zenn/image/fetch/s--zYgqsvLo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg","avatarUrl":"https://storage.googleapis.com/zenn-user-upload/avatar/dacf6ffc07.jpeg","bio":"Kannade Inc. CEO←フリーランス←カルテットコミュニケーションズ創業CTO。社外CTO/技術顧問数社。PHPとTypeScript。Symfonyの知見をよくブログに書きます。お仕事のご相談いつでもTwitter DMまで✉️詳細はリンク先にて","autolinkedBio":"Kannade Inc. CEO←フリーランス←カルテットコミュニケーションズ創業CTO。社外CTO/技術顧問数社。PHPとTypeScript。Symfonyの知見をよくブログに書きます。お仕事のご相談いつでもTwitter DMまで✉️詳細はリンク先にて","githubUsername":"ttskch","twitterUsername":"ttskch","isSupportOpen":true,"tokusyoContact":null,"tokusyoName":null,"websiteUrl":"https://ttskch.com","websiteDomain":"ttskch.com","totalLikedCount":1856,"gaTrackingId":"G-WZPLQEYQE9","hatenaId":null,"isInvoiceIssuer":false},"topics":[{"id":2,"name":"adobexd","taggingsCount":5,"imageUrl":"https://storage.googleapis.com/zenn-user-upload/topics/9d705b1166.png","displayName":"Adobe XD"},{"id":34,"name":"figma","taggingsCount":335,"imageUrl":"https://storage.googleapis.com/zenn-user-upload/topics/ea53fb4cd2.png","displayName":"Figma"},{"id":73,"name":"laravel","taggingsCount":2401,"imageUrl":"https://storage.googleapis.com/zenn-user-upload/topics/1303a86ae8.png","displayName":"Laravel"},{"id":90,"name":"php","taggingsCount":3347,"imageUrl":"https://storage.googleapis.com/zenn-user-upload/topics/21186e4563.png","displayName":"PHP"},{"id":15399,"name":"帳票","taggingsCount":22,"imageUrl":"https://zenn.dev/images/topic.png","displayName":"帳票"}],"isMine":false,"isPreview":false,"draftRevealScope":"private","githubUrl":"https://github.com/ttskch/zenn-content/blob/main/articles/1f1572cfd2e375.md","githubCommitsUrl":"https://github.com/ttskch/zenn-content/commits/main?path=articles/1f1572cfd2e375.md","githubRepository":{"id":4141,"name":"zenn-content","fullName":"ttskch/zenn-content","htmlUrl":"https://github.com/ttskch/zenn-content","branchName":"main","public":true,"showGithubLink":true},"currentUserLiked":false,"currentUserBookmarked":false,"comments":[],"commentedUsers":[],"positiveCommentsCount":0,"publication":null}},"page":"/[username]/articles/[slug]","query":{"username":"ttskch","slug":"1f1572cfd2e375"},"buildId":"6cWpQ5Z1LAtdbyaB4EE5X","assetPrefix":"https://static.zenn.studio","isFallback":false,"isExperimentalCompile":false,"dynamicIds":[83322],"gip":true,"scriptLoader":[]}</script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10