refactor several parts
This commit is contained in:
parent
35bfb83ca4
commit
8be562048a
|
@ -26,6 +26,7 @@ async function main(grade) {
|
||||||
let resultPage = '';
|
let resultPage = '';
|
||||||
for (kanji of texData) {
|
for (kanji of texData) {
|
||||||
resultPage+=`${kanji.kanjiPageHeader}
|
resultPage+=`${kanji.kanjiPageHeader}
|
||||||
|
${kanji.label}
|
||||||
${kanji.kanjiMeaning ? kanji.kanjiMeaning : ''}
|
${kanji.kanjiMeaning ? kanji.kanjiMeaning : ''}
|
||||||
${kanji.kunyomi ? kanji.kunyomi : ''}
|
${kanji.kunyomi ? kanji.kunyomi : ''}
|
||||||
${kanji.onyomi ? kanji.onyomi : ''}
|
${kanji.onyomi ? kanji.onyomi : ''}
|
||||||
|
|
|
@ -1,41 +1,40 @@
|
||||||
function makeNumberRow(rowLength) {
|
|
||||||
let numberRow = [...Array(rowLength).keys()]; // Array containing numbers 0 to rowLength-1
|
const makeNumberRow = (length) => {
|
||||||
numberRow = numberRow.map((number) => (number + 1).toString()); // Correct numbers and convert to string
|
const numberRow = [ ' ',
|
||||||
numberRow = numberRow.map((number) => `{\\large ${number}}`); // Encapsulate numbers in TeX code
|
...[...Array(length).keys()]
|
||||||
numberRow = [' ', ...numberRow];
|
.map(num => `{\\large ${num+1}}`),
|
||||||
|
' ' ].join(' & ');
|
||||||
return `
|
return `
|
||||||
${numberRow.join(' & ')} \\\\
|
${numberRow} \\\\
|
||||||
\\hline
|
\\hline
|
||||||
\\endhead\n`;
|
\\endhead\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function kanjiRow(index, rowLength, kanjiArray) {
|
const lastNonEmptyChar = (chars) => {
|
||||||
let result = [];
|
let index = chars.length - 1;
|
||||||
for (let rowIndex = 0; rowIndex < rowLength; rowIndex++) {
|
while (chars[index] == '') index--;
|
||||||
const currentIndex = index + rowIndex;
|
return chars[index]
|
||||||
result.push(kanjiArray[currentIndex] ? kanjiArray[currentIndex] : '');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRows(rowLength, columnLength, kanjiArray) {
|
const makeKanjiRow = (index, chars) =>
|
||||||
let result = '';
|
[ `{\\large ${index}}`,
|
||||||
for (let columnIndex = 0; columnIndex < columnLength; columnIndex++) {
|
...chars.map(chara => `\\hyperref[${chara}]{${chara}}`),
|
||||||
let line = new Array;
|
`p.\\pageref{${lastNonEmptyChar(chars)}}` ].join(' & ');
|
||||||
const index = columnIndex * rowLength;
|
|
||||||
|
|
||||||
// Add the number of current character
|
const splitBy = (array, num) => {
|
||||||
line.push(`{\\large ${index}}`);
|
let results = [];
|
||||||
|
while (array.length) {
|
||||||
// Concatenate the number with the rest of the row
|
results.push(array.splice(0, num));
|
||||||
line = [...line, ...kanjiRow(index, rowLength, kanjiArray)];
|
}
|
||||||
|
results[results.length-1].length = num;
|
||||||
// Convert the line array into a tex row and add it to result.
|
results[results.length-1] = Array.from(results[results.length-1], chara => chara || '');
|
||||||
result += `${line.join(' & ')} \\\\\n`;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
const makeRows = (length, kanjiArray) =>
|
||||||
}
|
[ ...splitBy(kanjiArray, length)
|
||||||
|
.map((row, i) => makeKanjiRow(i*length,row)),
|
||||||
|
'' ].join(' \\\\\n');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns an array of kanji into a tabular for a chapter overview
|
* Turns an array of kanji into a tabular for a chapter overview
|
||||||
|
@ -43,15 +42,15 @@ function makeRows(rowLength, columnLength, kanjiArray) {
|
||||||
* @param {number} rowLength The length of each row
|
* @param {number} rowLength The length of each row
|
||||||
* @returns {string} A tex tabular
|
* @returns {string} A tex tabular
|
||||||
*/
|
*/
|
||||||
function chapterTabular(kanjiArray, rowLength) {
|
function chapterTabular(kanjiArray, length) {
|
||||||
const columnLength = Math.ceil(kanjiArray.length/rowLength);
|
// const height = Math.ceil(kanjiArray.length/length);
|
||||||
|
|
||||||
let tabularString = '';
|
let tabularString = '';
|
||||||
|
|
||||||
tabularString += makeNumberRow(rowLength);
|
tabularString += makeNumberRow(length);
|
||||||
tabularString += makeRows(rowLength, columnLength, kanjiArray);
|
tabularString += makeRows(length, kanjiArray);
|
||||||
|
|
||||||
return `\\begin{chapterTabular}{ ${' l | ' + 'l '.repeat(rowLength)}}
|
return `\\begin{chapterTabular}{ ${' l | ' + 'l '.repeat(length + 1)}}
|
||||||
${tabularString}\\end{chapterTabular}`
|
${tabularString}\\end{chapterTabular}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,49 +1,40 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const jishoApi = require('unofficial-jisho-api');
|
const jishoApi = require('unofficial-jisho-api');
|
||||||
|
|
||||||
const jisho = new jishoApi();
|
const jisho = new jishoApi();
|
||||||
|
|
||||||
const txtFolder = './data/jouyou/';
|
const txtFolder = './data/jouyou/';
|
||||||
const jishoBufferFolder = './data/jisho/';
|
const jishoBufferFolder = './data/jisho/';
|
||||||
|
|
||||||
/* Async version of fs.readFile */
|
const fetchCharactersFromTxt = file => [...fs.readFileSync(file, 'utf8')];
|
||||||
const readFile = util.promisify(fs.readFile);
|
const fetchBufferedJishoResults = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
|
||||||
async function fetchCharactersFromTxt(file) {
|
const makeDelayedJishoRequest = (kanji, delay) =>
|
||||||
const data = await readFile(file, 'utf8');
|
new Promise(
|
||||||
return [...data];
|
(res, rej) => setTimeout(() => res(jisho.searchForKanji(kanji)), delay)
|
||||||
}
|
);
|
||||||
|
|
||||||
async function fetchBufferedJishoResults(file) {
|
|
||||||
const data = await readFile(file, 'utf8');
|
|
||||||
return JSON.parse(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeDelayedJishoRequest(kanji, delay) {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
setTimeout(() => { res(jisho.searchForKanji(kanji)); }, delay);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sort array of jisho results based on stroke count */
|
/* Sort array of jisho results based on stroke count */
|
||||||
const sortJishoResults = (jishoResult) => jishoResult.sort((a, b) => (a.strokeCount > b.strokeCount) ? 1 : -1);
|
const sortJishoResults = jishoResult => jishoResult.sort((a, b) => a.strokeCount > b.strokeCount);
|
||||||
|
|
||||||
/* Fetches Jisho results with a delay of 50ms between each request */
|
/* Fetches Jisho results with a delay of 50ms between each request */
|
||||||
async function fetchKanjiFromJisho(kanjiArray) {
|
const fetchKanjiFromJisho = async (kanjiArray) => {
|
||||||
const delayedRequests = kanjiArray.map(async (kanji, i) => await makeDelayedJishoRequest(kanji, i*50));
|
const delayedRequests = kanjiArray.map((kanji, i) => makeDelayedJishoRequest(kanji, i*50));
|
||||||
const data = await Promise.all(delayedRequests);
|
const data = await Promise.all(delayedRequests);
|
||||||
return sortJishoResults(data);
|
return sortJishoResults(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function fetchJishoDataAndWriteToBuffer(grade) {
|
async function fetchJishoDataAndWriteToBuffer(grade) {
|
||||||
const kanjiArray = await fetchCharactersFromTxt(`${txtFolder}${grade}.txt`);
|
const kanjiArray = fetchCharactersFromTxt(`${txtFolder}${grade}.txt`);
|
||||||
const jishoResults = await fetchKanjiFromJisho(kanjiArray);
|
const jishoResults = await fetchKanjiFromJisho(kanjiArray);
|
||||||
fs.writeFile(
|
|
||||||
|
fs.writeFileSync(
|
||||||
`${jishoBufferFolder}${grade}.json`,
|
`${jishoBufferFolder}${grade}.json`,
|
||||||
JSON.stringify(jishoResults, null, " "),
|
JSON.stringify(jishoResults, null, " "),
|
||||||
(err) => { if (err) console.error(err) }
|
(err) => { if (err) console.error(err) }
|
||||||
);
|
);
|
||||||
|
|
||||||
return jishoResults;
|
return jishoResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,10 +49,10 @@ async function fetchJishoResults(grade, log) {
|
||||||
const bufferFileExists = fs.existsSync(`${jishoBufferFolder}${grade}.json`);
|
const bufferFileExists = fs.existsSync(`${jishoBufferFolder}${grade}.json`);
|
||||||
|
|
||||||
if(bufferFileExists) {
|
if(bufferFileExists) {
|
||||||
log('Fetching Jisho data from buffer', grade)
|
log('Fetching Jisho data from buffer', grade);
|
||||||
return await fetchBufferedJishoResults(`${jishoBufferFolder}${grade}.json`);
|
return fetchBufferedJishoResults(`${jishoBufferFolder}${grade}.json`);
|
||||||
} else {
|
} else {
|
||||||
log('Fetching data from Jisho and writing to buffer', grade)
|
log('Fetching data from Jisho and writing to buffer', grade);
|
||||||
return await fetchJishoDataAndWriteToBuffer(grade);
|
return await fetchJishoDataAndWriteToBuffer(grade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,77 +5,77 @@ const stylingBrackets = {
|
||||||
const yomiConnector = '、 ';
|
const yomiConnector = '、 ';
|
||||||
const yomiDash = '—';
|
const yomiDash = '—';
|
||||||
|
|
||||||
const styleText = (string) => `\\emphasize{${string}}`;
|
const regexes = [
|
||||||
|
[/"|\[|\]/g, ''], // Remove all []
|
||||||
|
[/\\/g, '\\\\'], // Escape \
|
||||||
|
[/%/g, '\\%'], //Escape %
|
||||||
|
[/&/g, '\\&'], // Escape &
|
||||||
|
[/,/g, yomiConnector] // convert all , to yomiConnector
|
||||||
|
]
|
||||||
|
|
||||||
function styleCharactersBeforeDot(string) {
|
const styleText = string => `\\emphasize{${string}}`;
|
||||||
|
|
||||||
|
// ab.c -> \emph{ab}.c
|
||||||
|
const styleCharactersBeforeDot = string => {
|
||||||
const words = string.split('.');
|
const words = string.split('.');
|
||||||
words[0] = styleText(words[0]);
|
words[0] = styleText(words[0]);
|
||||||
return words.join('');
|
return words.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function styleEverythingExceptDash(string) {
|
// abc- -> \emph{abc}-
|
||||||
const words = string.split(/(?<=\-)/);
|
const styleEverythingExceptDash = string => {
|
||||||
if (words[0] === '-') { // ['-', 'word']
|
const parts = string.split(/(?<=\-)/);
|
||||||
words[0] = yomiDash;
|
if (parts[0] === '-') { // ['-', 'abc']
|
||||||
words[1] = styleText(words[1]);
|
parts[0] = yomiDash;
|
||||||
} else { // ['Word-', '']
|
parts[1] = styleText(parts[1]);
|
||||||
words[1] = yomiDash;
|
} else { // ['abc-', '']
|
||||||
words[0] = words[0].slice(0, words[0].length-1);
|
parts[1] = yomiDash;
|
||||||
words[0] = styleText(words[0]);
|
parts[0] = parts[0].slice(0, -1);
|
||||||
|
parts[0] = styleText(parts[0]);
|
||||||
}
|
}
|
||||||
return words.join('');
|
return parts.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertKunyomi(jishoResult) {
|
const convertKunyomi = jishoResult =>
|
||||||
|
jishoResult.kunyomi.length === 0
|
||||||
if (jishoResult.kunyomi.length === 0) return '';
|
? ''
|
||||||
|
: JSON.stringify(jishoResult.kunyomi)
|
||||||
const kunyomi = JSON.stringify(jishoResult.kunyomi)
|
|
||||||
.replace(/"|\[|\]/g, '')
|
.replace(/"|\[|\]/g, '')
|
||||||
.replace(/\\/g, '\\\\')
|
.replace(/\\/g, '\\\\')
|
||||||
.replace(/%/g, '\\%')
|
.replace(/%/g, '\\%')
|
||||||
.replace(/&/g, '\\&')
|
.replace(/&/g, '\\&')
|
||||||
.split(',');
|
.split(',')
|
||||||
|
.map(reading => {
|
||||||
|
if (reading.includes('.') && reading.includes('-'))
|
||||||
|
return `${yomiDash}${styleCharactersBeforeDot(reading.slice(1))}`
|
||||||
|
else if (reading.includes('.'))
|
||||||
|
return styleCharactersBeforeDot(reading);
|
||||||
|
else if (reading.includes('-'))
|
||||||
|
return styleEverythingExceptDash(reading);
|
||||||
|
else
|
||||||
|
return styleText(reading);
|
||||||
|
})
|
||||||
|
.join(yomiConnector);
|
||||||
|
|
||||||
for (const i in kunyomi) {
|
const convertOnyomi = jishoResult =>
|
||||||
instance = kunyomi[i];
|
JSON.stringify(jishoResult.onyomi)
|
||||||
|
|
||||||
if (instance.includes('.') && instance.includes('-')) {
|
|
||||||
//TODO: Apply combinated logic here
|
|
||||||
}
|
|
||||||
else if (instance.includes('.')) {
|
|
||||||
kunyomi[i] = styleCharactersBeforeDot(instance);
|
|
||||||
}
|
|
||||||
else if (instance.includes('-')) {
|
|
||||||
kunyomi[i] = styleEverythingExceptDash(instance);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
kunyomi[i] = styleText(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return kunyomi.join(yomiConnector);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertOnyomi(jishoResult) {
|
|
||||||
return JSON.stringify(jishoResult.onyomi)
|
|
||||||
.replace(/"|\[|\]/g, '')
|
.replace(/"|\[|\]/g, '')
|
||||||
.replace(/\\/g, '\\\\')
|
.replace(/\\/g, '\\\\')
|
||||||
.replace(/%/g, '\\%')
|
.replace(/%/g, '\\%')
|
||||||
.replace(/,/g, yomiConnector)
|
.replace(/&/g, '\\&')
|
||||||
.replace(/&/g, '\\&');
|
.split(',')
|
||||||
|
.map(styleText)
|
||||||
|
.join(yomiConnector);
|
||||||
|
|
||||||
//TODO: Style only the words, and not the yomiConnector inbetween
|
//TODO: Style only the words, and not the yomiConnector inbetween
|
||||||
}
|
|
||||||
|
|
||||||
function convertMeaning(jishoResult) {
|
const convertMeaning = jishoResult =>
|
||||||
return jishoResult.meaning
|
jishoResult.meaning
|
||||||
.replace(/\\/g, '\\\\')
|
.replace(/\\/g, '\\\\')
|
||||||
.replace(/%/g, '\\%')
|
.replace(/%/g, '\\%')
|
||||||
.replace(/&/g, '\\&');
|
.replace(/&/g, '\\&');
|
||||||
}
|
|
||||||
|
|
||||||
const makeFirstLetterUppercase = (string) => string.charAt(0).toUpperCase() + string.slice(1);
|
const makeFirstLetterUppercase = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate TeX strings from Jisho data
|
* Generate TeX strings from Jisho data
|
||||||
|
@ -83,25 +83,26 @@ const makeFirstLetterUppercase = (string) => string.charAt(0).toUpperCase() + st
|
||||||
* @param {string} grade
|
* @param {string} grade
|
||||||
* @returns {object} An object containg TeX strings
|
* @returns {object} An object containg TeX strings
|
||||||
*/
|
*/
|
||||||
function getKanjiTexData(jishoResults, grade) {
|
const getKanjiTexData = (jishoResults, grade) => {
|
||||||
|
|
||||||
|
grade = grade.slice(0,5) + ' ' + grade.slice(5); // graden -> grade n
|
||||||
|
grade = makeFirstLetterUppercase(grade);
|
||||||
|
if (grade === 'Grade 7') grade = 'Junior High';
|
||||||
|
|
||||||
return jishoResults.map(jishoResult => {
|
return jishoResults.map(jishoResult => {
|
||||||
|
|
||||||
const meaning = convertMeaning(jishoResult);
|
const meaning = convertMeaning(jishoResult);
|
||||||
const kunyomi = convertKunyomi(jishoResult);
|
const kunyomi = convertKunyomi(jishoResult);
|
||||||
const onyomi = convertOnyomi(jishoResult);
|
const onyomi = convertOnyomi(jishoResult);
|
||||||
|
|
||||||
grade = grade.slice(0,5) + ' ' + grade.slice(5);
|
|
||||||
grade = makeFirstLetterUppercase(grade);
|
|
||||||
if (grade === 'Grade 7') grade = 'Junior high'
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kanjiPageHeader: `\\kanjiPageHeader{${jishoResult.query}}{${grade}}{${jishoResult.jlptLevel}}{${jishoResult.strokeCount}}{${jishoResult.radical.symbol}}`,
|
kanjiPageHeader: `\\kanjiPageHeader{${jishoResult.query}}{${grade}}{${jishoResult.jlptLevel}}{${jishoResult.strokeCount}}{${jishoResult.radical.symbol}}`,
|
||||||
|
label: `\\label{${jishoResult.query}}`,
|
||||||
kanjiMeaning: meaning ? `\\kanjiMeaning{${meaning}}` : '',
|
kanjiMeaning: meaning ? `\\kanjiMeaning{${meaning}}` : '',
|
||||||
kunyomi: kunyomi ? `\\kunyomi{${kunyomi}}` : '',
|
kunyomi: kunyomi ? `\\kunyomi{${kunyomi}}` : '',
|
||||||
onyomi: onyomi ? `\\onyomi{${onyomi}}` : '',
|
onyomi: onyomi ? `\\onyomi{${onyomi}}` : '',
|
||||||
kanjiRow: `\\kanjiRow{${jishoResult.query}}`
|
kanjiRow: `\\kanjiRow{${jishoResult.query}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
kanjiLib.tex
10
kanjiLib.tex
|
@ -12,9 +12,7 @@
|
||||||
% Chapter Introduction %
|
% Chapter Introduction %
|
||||||
% ---------------------------------------------------------------------------- %
|
% ---------------------------------------------------------------------------- %
|
||||||
|
|
||||||
\setcounter{secnumdepth}{0}
|
\newcommand{\tocPiece}[2]{
|
||||||
|
|
||||||
\newcommand{\chapterIntroduction}[2]{
|
|
||||||
% For some reason, I'm not able to use \uppercase in order to use
|
% For some reason, I'm not able to use \uppercase in order to use
|
||||||
% a single argument. In the end, I found that just using two arguments
|
% a single argument. In the end, I found that just using two arguments
|
||||||
% was going to be easier than trying to override the way the LaTeX
|
% was going to be easier than trying to override the way the LaTeX
|
||||||
|
@ -23,11 +21,15 @@
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\fontsize{16}{16}
|
\fontsize{16}{16}
|
||||||
|
\rowcolors{1}{}{kanjiColor!20!white}
|
||||||
\input{./data/tables/#2.tex}
|
\input{./data/tables/#2.tex}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
}
|
||||||
|
|
||||||
\localtableofcontents
|
\setcounter{secnumdepth}{0}
|
||||||
|
|
||||||
|
\newcommand{\chapterIntroduction}[2]{
|
||||||
|
\tocPiece{#1}{#2}
|
||||||
\break
|
\break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
main.tex
23
main.tex
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
\input{./kanjiLib.tex}
|
\input{./kanjiLib.tex}
|
||||||
|
|
||||||
\usepackage{etoc} % For local tocs containing level based kanji list.
|
% \usepackage{etoc} % For local tocs containing level based kanji list.
|
||||||
|
|
||||||
\begin{document}
|
\begin{document}
|
||||||
|
|
||||||
|
@ -43,7 +43,14 @@
|
||||||
|
|
||||||
\break
|
\break
|
||||||
|
|
||||||
\tableofcontents
|
\tocPiece{Grade 1}{grade1}
|
||||||
|
\tocPiece{Grade 2}{grade2}
|
||||||
|
\tocPiece{Grade 3}{grade3}
|
||||||
|
\tocPiece{Grade 4}{grade4}
|
||||||
|
\tocPiece{Grade 5}{grade5}
|
||||||
|
\tocPiece{Grade 6}{grade6}
|
||||||
|
\tocPiece{Junior High}{grade7}
|
||||||
|
|
||||||
\break
|
\break
|
||||||
|
|
||||||
\section*{Preface}
|
\section*{Preface}
|
||||||
|
@ -60,21 +67,21 @@
|
||||||
\input{./data/pages/grade1.tex}
|
\input{./data/pages/grade1.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Grade 2}{grade2}
|
\chapterIntroduction{Grade 2}{grade2}
|
||||||
% \input{./data/pages/grade2.tex}
|
\input{./data/pages/grade2.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Grade 3}{grade3}
|
\chapterIntroduction{Grade 3}{grade3}
|
||||||
% \input{./data/pages/grade3.tex}
|
\input{./data/pages/grade3.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Grade 4}{grade4}
|
\chapterIntroduction{Grade 4}{grade4}
|
||||||
% \input{./data/pages/grade4.tex}
|
\input{./data/pages/grade4.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Grade 5}{grade5}
|
\chapterIntroduction{Grade 5}{grade5}
|
||||||
% \input{./data/pages/grade5.tex}
|
\input{./data/pages/grade5.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Grade 6}{grade6}
|
\chapterIntroduction{Grade 6}{grade6}
|
||||||
% \input{./data/pages/grade6.tex}
|
\input{./data/pages/grade6.tex}
|
||||||
|
|
||||||
\chapterIntroduction{Junior High}{grade7}
|
\chapterIntroduction{Junior High}{grade7}
|
||||||
% \input{./data/pages/grade7.tex}
|
\input{./data/pages/grade7.tex}
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
Loading…
Reference in New Issue