Skip to content

Instantly share code, notes, and snippets.

@aiya000
Last active September 8, 2019 09:26
Show Gist options
  • Save aiya000/801bec5a9b869eb70f30dcb1660898ba to your computer and use it in GitHub Desktop.
Save aiya000/801bec5a9b869eb70f30dcb1660898ba to your computer and use it in GitHub Desktop.
会話風「キャラクター + ふきだし」 in Re:VIEW css-typesetting https://gyazo.com/163677e435eef1bcac18ca034dc1112e
@media print {
body {
font-family: 'Noto Sans CJK JP', sans-serif;
}
/* @import url(https://fonts.googleapis.com/earlyaccess/notosansjp.css); */
/*
* Noto Sans (japanese) http://www.google.com/fonts/earlyaccess
*/
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 100;
src: local("Noto Sans CJK JP Thin"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Thin.otf) format('opentype');
}
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 300;
src: local("Noto Sans CJK JP Light"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Light.otf) format('opentype');
}
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 400;
src: local("Noto Sans CJK JP Regular"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Regular.otf) format('opentype');
}
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 500;
src: local("Noto Sans CJK JP Medium"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Medium.otf) format('opentype');
}
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 700;
src: local("Noto Sans CJK JP Bold"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Bold.otf) format('opentype');
}
@font-face {
font-family: 'Noto Sans CJK JP';
font-style: normal;
font-weight: 900;
src: local("Noto Sans CJK JP Black"),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.woff2) format('woff2'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.woff) format('woff'),
url(https://fonts.gstatic.com/ea/notosansjp/v5/NotoSansJP-Black.otf) format('opentype');
}
}
body {
page-break-before: left;
counter-reset: footnote;
}
@media screen {
:root {
line-height: 1.3;
}
.print-only {
display: none;
}
}
@media print {
:root {
font-weight: 400;
line-height: 1.5;
padding-top: 1.5rem;
}
/* A5設定 */
:root {
font-size: 9pt;
}
a {
text-decoration: none;
font-style: italic;
}
a:link,
a:visited {
color: black;
}
}
@media print {
.titlepage-page > .titlepage {
text-align: center;
}
}
.titlepage-page > .titlepage > h1 {
font-size: 2.5rem;
}
.titlepage-page > .titlepage > .author {
font-size: 2rem;
}
.titlepage-page > .titlepage > .illustrator {
font-size: 2rem;
}
@media print {
.colophon-page > .colophon {
text-align: right;
}
}
.colophon-page > .colophon > h1 {
font-size: 1.8rem;
}
.colophon-page > .colophon > hr {
width: 50%;
margin-right: 0;
border: 1px solid black;
}
section {
break-before: page;
break-after: page;
}
nav {
break-before: page;
break-after: page;
}
aside {
break-before: page;
break-after: page;
}
p.footnote {
float: footnote;
}
a.noteref {
vertical-align: super;
}
/* NOTE footnoteの自動採番がやりたかった奴〜〜 */
/*
section {
counter-reset: footnote;
}
p.footnote {
counter-increment: footnote;
}
p.footnote::footnote-marker {
content: '* ' counter(footnote);
}
a.noteref::before {
content: '*' target-counter(attr(href), footnote);
vertical-align: super;
}
*/
nav.toc a::after {
content: target-counter(attr(href), page);
float: right;
}
p {
text-indent: 1rem;
text-align: justify;
margin-top: 0.3rem;
margin-bottom: 0;
}
/* .image, */
p.caption {
text-align: center;
text-indent: 0;
}
/* list, emlist, tableのキャプションは左寄せ */
.caption-code p.caption,
.emlist-code p.caption {
text-align: start;
}
p.caption::before {
color: gray;
content: "▲";
}
.caption-code p.caption::before,
.emlist-code p.caption::before,
.table p.caption::before {
color: gray;
content: "▲";
}
pre.cmd {
font-size: 0.8rem;
background-color: #444;
color: white;
padding: 1rem 2rem;
}
pre.list, pre.emlist {
font-family: 'Noto Sans Mono CJK JP', monospace; /* :D */
padding: 1rem 2rem;
background-color: #eee;
border: solid 3px gray;
border-radius: 0.3rem;
}
table {
margin: 0 auto 2em auto;
border-collapse: collapse;
}
table tr th {
border: 1px black solid;
font-size: 0.9rem;
padding: 0.3rem;
}
table tr td {
border:1px black solid;
font-size: 0.9rem;
padding: 0.3rem;
}
p.tablecaption, table caption {
color: #666;
font-weight: bold;
text-indent: 0;
}
div.image {
text-align: center;
}
div.column {
margin: 10px;
padding: 0.8rem 1.5rem;
border: solid 3px gray;
border-radius: 0.3rem;
}
div.column h4 {
margin-top: 0;
}
/* ブロック系の途中で改ページされるのを避ける。本当はかかってもいいんだけど脚注がある時に背景がぶっ壊れる… */
pre.cmd {
page-break-inside: avoid;
}
pre.list, pre.emlist {
page-break-inside: avoid;
}
table {
page-break-inside: avoid;
}
@page {
size: B5;
/* トンボ */
/* marks: crop cross; */
/* 裁ち落としのとこまで塗りたかったら pentapod本を トンボ で検索 */
}
@page {
margin: 10mm;
margin-top: 15mm;
}
/* A4設定 */
/*
@page :left {
margin-left: 30mm;
padding: 1rem;
}
@page :right {
margin-right: 30mm;
padding: 1rem;
}
*/
/* A5設定 */
@page :left {
margin-left: 15mm;
padding: 1rem;
}
@page :right {
margin-right: 15mm;
padding: 1rem;
}
@page :left {
@top-left {
/* 本当は現在の深さ1の見出しを出したい */
/* かつ、sectionの1ページ目だったら出したくない */
content: "せつラボ";
vertical-align: bottom;
border-bottom: solid 1px black;
}
@top-right {
content: " "; /* 内容ないと枠でない */
border-bottom: solid 1px black;
}
@top-center {
content: " "; /* 内容ないと枠でない */
border-bottom: solid 1px black;
}
@bottom-center {
content: "←" counter(page);
}
}
@page :right {
@top-right {
/* 本当は現在の深さ2の見出しを出したい */
/* かつ、sectionの1ページ目だったら出したくない */
content: "せつラボ";
vertical-align: bottom;
border-bottom: solid 1px black;
}
@top-left {
content: " "; /* 内容ないと枠でない */
border-bottom: solid 1px black;
}
@top-center {
content: " "; /* 内容ないと枠でない */
border-bottom: solid 1px black;
}
@bottom-center {
content: counter(page) "→";
}
}
/* NOTE これイケるのでは?と思ったけどダメだった
@page foobar:right {
@top-right {
content: "章タイトルだよー";
}
}
section.foobar {
page: foobar;
}
*/
/* NOTE これもイケるのでは?と思ったけどサポートされてなさそう
@page :right {
@top-right {
content: element(chaptitle);
}
}
section h1 {
position: running(chaptitle);
}
*/
@page :first {
padding-top: 0mm;
}
/* 1ページ目は扉なのでページ上部の装飾無し */
@page :first {
@top-right {
content: " ";
border-bottom: none;
}
@top-left {
content: " ";
border-bottom: none;
}
@top-center {
content: " ";
border-bottom: none;
}
}
/* TODO 最終ページは奥付なのでページ上部の装飾無し */
/* …はてどうやるんだ??名前付きページはまだサポートされてなさそう… */
/*
section.colophon-page {
page: colophon;
}
@page colophon {
@top-right {
content: " ";
border-bottom: none;
}
@top-left {
content: " ";
border-bottom: none;
}
@top-center {
content: " ";
border-bottom: none;
}
}
*/
.width-010per { width: 10%; }
.width-020per { width: 20%; }
.width-025per { width: 25%; }
.width-030per { width: 30%; }
.width-033per { width: 33%; }
.width-040per { width: 40%; }
.width-050per { width: 50%; }
.width-060per { width: 60%; }
.width-067per { width: 67%; }
.width-070per { width: 70%; }
.width-075per { width: 75%; }
.width-080per { width: 80%; }
.width-090per { width: 90%; }
.width-100per { width: 100%; }
/* It's me, aiya000 :D */
.book-toc li {
list-style: none;
}
.child-chapter {
list-style: none;
}
.inline-code {
font-family: 'Noto Sans Mono CJK JP';
}
/* review-ext.rb */
.talk-left {
width: 100%;
margin: 10px;
overflow: hidden;
}
.talk-left-speaker {
display: inline-block;
vertical-align: top;
}
.talk-left-speaker img {
width: 2.0cm;
}
.talk-left-sentence {
display: inline-block;
position: relative;
padding: 10px;
margin: 0 0 0 10px;
border-radius: 12px;
background: whitesmoke;
max-width: 65%;
}
.talk-left-sentence:after {
content: "";
position: absolute;
top: 15px;
left: -19px;
border: 8px solid transparent;
border-right: 18px solid whitesmoke;
transform: rotate(-15deg);
}
.talk-left-sentence p {
padding: 1.5px 0 1.5px 0;
text-indent: 0;
}
.talk-left-sentence-desc p {
text-indent: 10px;
}
.talk-right {
width: 100%;
margin: 10px;
overflow: hidden;
text-align: right;
}
.talk-right-sentence {
display: inline-block;
position: relative;
padding: 10px;
margin: 0 10px 0 0;
border-radius: 12px;
background: whitesmoke;
max-width: 65%;
text-align: left;
}
.talk-right-sentence:after {
content: "";
position: absolute;
top: 15px;
right: -19px;
border: 8px solid transparent;
border-left: 18px solid whitesmoke;
transform: rotate(15deg);
}
.talk-right-sentence p {
padding: 1.5px 0 1.5px 0;
text-indent: 0;
}
.talk-right-sentence-desc p {
text-indent: 10px;
}
.talk-right-speaker {
display: inline-block;
vertical-align: top;
}
.talk-right-speaker img {
width: 2.0cm;
}
.attention {
padding: 10px 0;
}
.attention-line {
text-align: center;
}
.romanlist {
counter-reset: list;
margin: 16px 0 16px -4px; /* this margin-left fixes a difference between this and '*' '1.' list */
border: solid 1px whitesmoke;
}
.romanlist li {
list-style: none;
margin: 2px 4px;
}
.romanlist li:before {
content: counter(list) ") ";
counter-increment: list;
}
.character-intro {
position: relative;
filter: grayscale(100%);
}
.character-intro img {
position: absolute;
top: 0;
right: 0;
width: 128px;
height: 128px;
border-radius: 10px;
border: solid 1px black;
}
.focus {
text-align: center;
}
.picture {
margin: 1.1cm;
padding: 0.1cm;
border: solid 1px black;
}
.picture img {
width: 100%;
}
# encoding: utf-8
ReVIEW::Compiler.defblock :talkleft, 1
ReVIEW::Compiler.defblock :talkright, 1
ReVIEW::Compiler.defblock :attention, 0
ReVIEW::Compiler.defblock :romanlist, 0
ReVIEW::Compiler.defblock :charintro, 1
ReVIEW::Compiler.defblock :list, 3
ReVIEW::Compiler.defsingle :focus, 0..1
ReVIEW::Compiler.definline :mathcode
ReVIEW::Compiler.definline :rubycode
module ReVIEW
class HTMLBuilder
# LINEっぽく、アイコンがしゃべってるみたいに、
# セリフ内容を吹き出しで囲うやつ。
#
# η専用。
# 空行に区切られた各文節を<p></p>する。
#
# linesが2行以上場合はηの口調が説明的であるので、字下げをオンにして見やすくするよ。
# (1行のみのセリフを字下げすると、時差現が変な左の空白に見えるため)
def talkleft(lines, who)
speaker =
case who
# srcは64pxの画像
when 'eta'
{alt: 'η', src: 'images/eta.png'}
when 'eta-arms'
{alt: 'η∞', src: 'images/eta-arms.png'}
when 'eta-sure'
{alt: 'η:)', src: 'images/eta-sure.png'}
when 'eta-smile'
{alt: 'η=)', src: 'images/eta-smile.png'}
when 'eta-light'
{alt: 'η=D', src: 'images/eta-light.png'}
when 'eta-thinking'
{alt: 'η:(', src: 'images/eta-thinking.png'}
when 'eta-blushed'
{alt: 'η…', src: 'images/eta-blushed.png'}
when 'eta-waver'
{alt: 'η…💦', src: 'images/eta-waver.png'}
when 'eta-waver-very'
{alt: 'η…💦💦', src: 'images/eta-waver-very.png'}
when 'invisible'
# 演出等に、アイコンを出したくない時など
{alt: '', src: ''}
else
raise "an unknown lefty speaker '#{who}'"
end
# TODO: 「行が2行以上」じゃなくて「\n\n」を確認する。(節が2つ以上)
talk_left_sentence_desc = -> (child){
if lines.length >= 2 then
%Q(<div class="talk-left-sentence-desc">#{child}</div>)
else
child
end
}
sentence = insert_space_after_japanese_punctuation(
talk_left_sentence_desc.(make_talk_sentence(lines))
)
puts <<~EOS
<div class="talk-left">
<div class="talk-left-speaker">
<img alt="#{speaker[:alt]}" src="#{speaker[:src]}"/>
</div>
<div class="talk-left-sentence">
#{sentence}
</div>
</div>
EOS
end
# talkleftと同じだけど、η以外。
def talkright(lines, who)
speaker =
case who
when 'mu'
{alt: 'μ', src: 'images/mu.png'}
when 'mu-saying'
{alt: 'μ「」', src: 'images/mu-saying.png'}
when 'mu-love'
{alt: 'μ//', src: 'images/mu-love.png'}
when 'mu-heart'
{alt: 'μ♡', src: 'images/mu-heart.png'}
when 'mu-wink'
{alt: 'μ;)', src: 'images/mu-wink.png'}
when 'mu-note'
{alt: 'μ♪', src: 'images/mu-note.png'}
when 'mu-smile'
{alt: 'μ:)', src: 'images/mu-smile.png'}
when 'mu-question'
{alt: 'μ?', src: 'images/mu-question.png'}
when 'somebody'
# 現時点では誰が発声したのかわからない時など
{alt: '', src: ''}
else
raise "an unknown rightly speaker '#{who}'"
end
talk_right_sentence_desc = -> (child){
if lines.length >= 2 then
%Q(<div class="talk-right-sentence-desc">#{child}</div>)
else
child
end
}
sentence = insert_space_after_japanese_punctuation(
talk_right_sentence_desc.(make_talk_sentence(lines))
)
puts <<~EOS
<div class="talk-right">
<div class="talk-right-sentence">
#{sentence}
</div>
<div class="talk-right-speaker">
<img alt="#{speaker[:alt]}" src="#{speaker[:src]}"/>
</div>
</div>
EOS
end
# @param [string] pos 'left' or 'right'
# @param [{alt: string, src: string}] speaker an img
#
# >>> make_talk_sentence(['ああ、', 'うん。', '', 'そう。'])
# "<div>ああ、うん。</div><div>そう。</div>"
private def make_talk_sentence(lines)
clauses = splitBy(lines, ->(x){ x == '' }).map {|clause|
clause.join
}
clauses.map {|clause|
"<p>#{clause}</p>"
}.join
end
# @param [array of T] xs
# @param [T -> bool] p a predicate for xs
#
# >>> p splitBy([10, 20, 0, 10, 0, 20], ->(x){ x == 0 })
# [[10, 20], [10], [20]]
private def splitBy(xs, p)
result = []
child = []
for x in xs do
if p.(x) then
result += [child]
child = []
else
child += [x]
end
end
result += [child]
result
end
def attention(lines)
children = lines.map {|line|
%Q(<p class="attention-line">#{line}</p>)
}
puts %Q(<div class="attention">#{children.join}</div>)
end
def romanlist(lines)
list_items = lines.map {|line|
%Q(<li>#{line}</li>)
}
puts %Q(<ul class="romanlist">#{list_items.join}</ul>)
end
def charintro(lines, image_path)
sentence = lines.map {|line|
%Q(<p>#{line}</p>)
}.join
puts %Q(
<div class="character-intro">
<div>#{sentence}</div>
<img src="#{image_path}" alt="キャラクターアイコン" />
</div>
)
end
def list(lines, id, caption, lang = nil)
puts %Q(<div id="#{normalize_id(id)}" class="caption-code">)
math_lines = lines.map {|line| mathematical_code(line)}
list_body id, math_lines, lang
begin
list_header id, caption, lang
rescue KeyError
error "no such list: #{id}"
end
puts '</div>'
end
# ηの考え事が中心の章から、μとの会話が中心の章へ移る時に、
# 後者の章の冒頭に入れるやつ。
def focus(symbol = '◆')
puts %Q(<p class="focus">#{symbol} #{symbol} #{symbol}</p>)
end
# A kind of `code`.
# Regards a suffix number to a sup
def inline_mathcode(s)
%Q(<code class="inline-code tt">#{mathematical_code(s)}</code>)
end
def inline_rubycode(s)
%Q(<code class="inline-code tt">#{inline_ruby(s)}</code></ruby>)
end
# # footnoteの採番をブラウザでやらせようとして挫折
# # vivliostyle.jsがIDを書き換えてしまい、参照できなくなってしまう
# # target-counter を使ったやりかた
# def footnote(id, str)
# puts %Q(<p class="footnote" id="fn-#{normalize_id(id)}">
# <span>#{compile_inline(str)}</span></p>)
# end
# def inline_fn(id)
# # 番号はCSSで出すので気にしない
# %Q(<a id="fnb-#{normalize_id(id)}" href="#fn-#{normalize_id(id)}"
# class="noteref"><!--#{@chapter.footnote(id).number}--></a>)
# end
end
module BuilderOverride
Compiler.definline :bruby
def inline_bruby(s)
"<b>#{inline_ruby(s)}</b>"
end
end
class Builder
prepend BuilderOverride
end
end
def insert_space_after_japanese_punctuation(sentence)
punctuations = '!?♪'
sentence.gsub(/([#{punctuations}])([^\s 」』…#{punctuations}])/, '\1 \2')
end
def mathematical_code(s)
names = '[A-Za-zαβγδεζηθικλμνξοπρστυφχψω≦≧<>]'
indice = '[0-9a-zA-Z\-,]'
mathematical = s.gsub(/(#{names})_(#{indice})/, '\1<sub>\2</sub>')
mathematical = mathematical.gsub(/(#{names})\^(#{indice})/, '\1<sup>\2</sup>')
mathematical = mathematical.gsub(/(#{names})_\${(#{indice}+)}\$/, '\1<sub>{\2}</sub>')
mathematical = mathematical.gsub(/(#{names})\^\${(#{indice}+)}\$/, '\1<sup>{\2}</sup>')
mathematical = mathematical.gsub(/(#{names})_{(#{indice}+)}/, '\1<sub>\2</sub>')
mathematical = mathematical.gsub(/(#{names})\^{(#{indice}+)}/, '\1<sup>\2</sup>')
mathematical
end
= 始まり
静寂な朝。
白基調のログハウス、高さ5メートル、風がよく通った部屋。
青くて白い、春の空。
//talkright[mu-smile]{
お風呂上がったよ〜。
//}
彼女が歩いてくる。
歩きながら揺れる、リボンでくくったふわふわの髪を、目が追う。
//talkleft[eta-arms]{
……。
//}
肩や首にかかる、なだらかな曲線からする、フローラルな香り。
μと一緒に暮らし始めてから何年も経つっていうのに、いつまでも慣れはしない。
ドキドキしちゃうから、少しクールぶって言葉を返す。
//talkleft[eta-smile]{
おかえり、μ。
//}
//talkright[mu-wink]{
ただいまη♪
//}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment